Skip to content

fix: relocate global Rules/Workflows/Hooks from ~/Documents to ~/.dirac (FU-9) - #174

Merged
dirac-run merged 3 commits into
dirac-run:masterfrom
alexdim:fix/fu9-rules-tcc-protected-path
Aug 13, 2026
Merged

fix: relocate global Rules/Workflows/Hooks from ~/Documents to ~/.dirac (FU-9)#174
dirac-run merged 3 commits into
dirac-run:masterfrom
alexdim:fix/fu9-rules-tcc-protected-path

Conversation

@alexdim

@alexdim alexdim commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Relocate ensureRulesDirectoryExists, ensureWorkflowsDirectoryExists, and ensureHooksDirectoryExists from ~/Documents/Dirac/{Rules,Workflows,Hooks} to ~/.dirac/{Rules,Workflows,Hooks} via getDiracHomePath().
  • Best-effort recursive migration from legacy ~/Documents/Dirac/<subdir>: copies files with COPYFILE_EXCL (idempotent, no clobber), swallows only ENOENT/EPERM/EACCES on the legacy path, logs and rethrows unexpected errors.
  • Classify both legacy Dirac/Hooks and new .dirac/Hooks as global hooks.
  • Remove the broken catch fallback that re-resolved the same TCC-protected path on mkdir failure.

Why

~/Documents is TCC-protected on macOS since Catalina (10.15). Any process without Full Disk Access gets EPERM on readdir/fs.watch against paths under ~/Documents, even when the directory exists and is readable interactively. This caused ruleLoadErrors to fire on every task run:

{"ruleLoadErrors":["Failed to read .diracrules directory at /Users/.../Documents/Dirac/Rules: Error reading directory at ...: EPERM: operation not permitted, scandir ..."]}

The existing catch fallback re-resolved the same ~/Documents/Dirac/Rules path, so it could never recover from TCC denial. Telling users to grant Full Disk Access is the wrong fix — it's overbroad (gives access to all protected paths, not just the one Dirac needs) and masks the real issue: global config shouldn't live on a TCC-protected path in the first place.

~/.dirac is already used by ensureSettingsDirectoryExists, ensureStateDirectoryExists, and ensureCacheDirectoryExists via getGlobalStorageDir(). Rules/Workflows/Hooks were the only outliers under ~/Documents.

Review follow-ups

  • Nested Rules/Workflows/Hooks dirs migrate recursively (no EISDIR skip).
  • HookRegistry.isGlobalHooksDir recognizes ~/.dirac/Hooks (and legacy Dirac/Hooks).
  • Only expected legacy-path failures (ENOENT/EPERM/EACCES) are swallowed; unexpected errors (e.g. EIO, ENOSYS) are logged and rethrown so ensureDiracSubdir fails loudly instead of reporting success on incomplete migration.
  • Coverage for nested migration, new global-hook path classification, EACCES, unexpected-error rejection, and per-file copy isolation.

Changes

src/core/storage/directoryEnsurers.ts

  • ensureRulesDirectoryExists / ensureWorkflowsDirectoryExists / ensureHooksDirectoryExists resolve ~/.dirac/{Rules,Workflows,Hooks}.
  • migrateDir + migrateFile: recursive migration with per-file isolation; destination files never clobbered.
  • migrateFromDocumentsDir: swallows only expected legacy-path errors; logs and rethrows unexpected ones.

src/core/hooks/HookRegistry.ts

  • isGlobalHooksDir matches both .dirac/Hooks and Dirac/Hooks.

Tests

  • directoryEnsurers.test.ts — path resolution, creation, migration, idempotency, EPERM/EACCES, nested dirs, per-file isolation, unexpected-error rejection.
  • HookRegistry.test.ts — legacy path, new .dirac path, workspace rejection.

Test plan

  • directoryEnsurers.test.ts — 30/30 passing
  • HookRegistry.test.ts — 3/3 passing
  • biome on touched files — 0 new issues
  • tsc unit-test project — only pre-existing src/shared/storage/types.ts baseline error

@dirac-run dirac-run left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting follow-up for three migration gaps:

  1. Rules and workflows are loaded recursively, but this migration copies only immediate readdir entries. Nested directories hit EISDIR and are skipped, so nested global configuration stops loading after relocation.
  2. HookRegistry.isGlobalHooksDir() recognizes .../Dirac/Hooks, but not the new .../.dirac/Hooks path. Relocated global hooks are therefore classified as workspace hooks.
  3. The migration currently catches every read error and swallows every non-EEXIST copy failure, although only expected legacy-path failures such as ENOENT/EPERM should be ignored.

Please add coverage for nested migration and global-hook classification at the new path.

@alexdim
alexdim force-pushed the fix/fu9-rules-tcc-protected-path branch from 86f1dcb to 3815d0c Compare August 12, 2026 16:04
@dirac-run

Copy link
Copy Markdown
Owner

Follow-up needed:

One migration error-contract issue remains. migrateFromDocumentsDir() logs unexpected failures but then returns normally, so an EIO, ENOSYS, or similar failure from migrateDir() is still swallowed and ensureDiracSubdir() reports success despite an incomplete migration.

Please ignore only ENOENT/EPERM/EACCES; log and rethrow other errors. The unexpected-error test should expect rejection rather than successful completion.

Alexandros Salapatas added 3 commits August 13, 2026 00:00
…ac (FU-9)

~/Documents is TCC-protected on macOS since Catalina. Processes without
Full Disk Access get EPERM on readdir/watch, causing ruleLoadErrors on
every task run. The existing catch fallback re-resolved the same
protected path, so it could never recover.

Move ensureRules/Workflows/HooksDirectoryExists to ~/.dirac/{Rules,
Workflows,Hooks} (already used by settings/state/cache via
getDiracHomePath). Add best-effort migration from the legacy
~/Documents/Dirac/<subdir> location — copies files with COPYFILE_EXCL
(idempotent, no clobber) and swallows EPERM on the legacy readdir.
- recurse nested Rules/Workflows/Hooks dirs (no EISDIR skip)
- classify ~/.dirac/Hooks as global hooks
- swallow only ENOENT/EPERM/EACCES; log unexpected
log + rethrow non-ENOENT/EPERM/EACCES so incomplete
migration fails loudly instead of fake success.
test now expects rejection.
@alexdim
alexdim force-pushed the fix/fu9-rules-tcc-protected-path branch from 3815d0c to ec49204 Compare August 12, 2026 21:00
@dirac-run

Copy link
Copy Markdown
Owner

One issue remains: migrateFromDocumentsDir() suppresses any escaping ENOENT/EPERM/EACCES, including errors from writing the new destination. A destination-side failure while creating a nested directory can therefore be swallowed and incomplete migration reported as success.

I’m merging this and will fix it forward before release by limiting expected-error suppression to legacy source reads, allowing destination failures to propagate, and adding regression coverage for destination permission failures.

@dirac-run
dirac-run merged commit c8e282c into dirac-run:master Aug 13, 2026
@alexdim
alexdim deleted the fix/fu9-rules-tcc-protected-path branch August 13, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants