macOS: long-running session progressively loses TCC-protected paths (Downloads → Documents → project dir), ending in getcwd() EPERM — grant and code signature both verified valid at denial time
Preflight Checklist
- [x] I have searched existing issues. Closest: #75374 (same mechanism family — auto-updater vs. tccd code-signature validation — but desktop-app install, external APFS volume,
SystemPolicyRemovableVolumes) and #84741 (same terminal state — session-wide EPERM, no in-session recovery — but triggered bygit worktree remove). This report is the npm-global install + TCC-protected home folder variant, and it adds evidence neither has: at the moment of denial I verified that the TCC grant existed and that the stored code requirement still validated against the on-disk binary. So "the grant is missing" and "the signature no longer matches" are both ruled out. - [x] Single bug report
- [x] Recent version
Environment
- Claude Code 2.1.232 → 2.1.233 (auto-updated mid-session), npm-global install at
/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe - macOS 26.5.1 (Darwin 25.5.0), Apple silicon
- Launched from Terminal.app; process ancestry
zsh ← claude ← -zsh ← login ← Terminal.app - Full Disk Access granted to both Terminal.app and
claude.exe - No
sandboxkey and nodenyReadin any settings.json
What's Wrong?
Over one long-lived session (~9 h), the process tree progressively lost access to TCC-protected paths, ending in total filesystem failure. Nothing on the machine changed: both TCC databases kept an mtime of Aug 4 15:15:28 throughout, so no grant was added, removed or re-prompted at any point.
| Time | State |
|---|---|
| 11:51:54 | session starts; everything normal for ~8 h |
| ~20:24 | ~/Downloads and ~/Documents → Operation not permitted. ~/Desktop fine. Non-TCC dirs (~/Music, ~/Pictures, ~/Public, ~/Library) fine |
| 20:43:46 | auto-update rewrites bin/claude.exe (2.1.232→2.1.233) under the running process (pid unchanged, started 11:51:54) |
| ~20:45 | ~/Downloads recovers, 5/5 clean. ~/Documents still denied, 5/5 |
| ~21:0x | getcwd() EPERM — every external binary dies before its body runs |
| after ⌘Q + relaunch | all three folders read fine immediately |
The kernel attributes the denial to claude.exe and to its children:
System Policy: claude.exe(40153) deny(1) file-read-data /Users/jbk/Documents
System Policy: ls(57439) deny(1) file-read-data /Users/jbk/Documents
tccd logged no corresponding request for those accesses (/usr/bin/log show --info --debug --predicate 'subsystem == "com.apple.TCC"' showed unrelated apps' requests in the same window, e.g. an AlDente AllFiles preflight, but nothing for this access). So the denial is decided without a TCC daemon round-trip — consistent with #75374's "attribution falls back to a client with no TCC record."
What I ruled out at denial time:
- Missing grant. System TCC db:
kTCCServiceSystemPolicyAllFiles | …/claude-code/bin/claude.exe | 2 | 2026-05-27 07:20:29, and the same forcom.apple.Terminal. Both hold FDA. - Stale code requirement. The csreq stored in TCC decodes to
identifier "com.anthropic.claude-code" and anchor apple generic … certificate leaf[subject.OU] = Q6L2SF6YDW, byte-for-byte the binary's designated requirement, andcodesign --verify -R <req> claude.exereturns satisfied — against the post-update binary. - Claude Code's own Seatbelt profile. Extracted from the binary:
(version 1),(deny default (with message "${logTag}")), Chrome-derived — but containing a blanket(allow file-read*)and no deny rule naming Downloads or Documents. The stringSystem Policydoes not appear in the binary at all, so that kernel label is macOS's, not the harness's log tag.
No in-session recovery. dangerouslyDisableSandbox: true fails identically (expected — this is System Policy, above Seatbelt). cd /private/tmp && git -C <path> status fails identically, because getcwd() is already broken. The shell's pwd builtin keeps printing a plausible path (it reads $PWD), which masks the breakage; /bin/pwd and any external binary fail. Only quitting and relaunching Terminal restores anything.
What Should Happen?
- The session's TCC attribution should remain stable for the life of the process — a long-running session should not silently lose access to folders it was granted, one at a time.
- When attribution can't be resolved, the failure should be surfaced, not returned as a bare
EPERM. A CLI can't raise a TCC prompt, so the user sees "Operation not permitted" and reasonably concludes their permissions are misconfigured. In this session that misdiagnosis cost roughly an hour and produced three wrong conclusions before the kernel log was read correctly. - An in-session recovery path, or at minimum a detectable signal the harness can report ("filesystem access degraded — start a new session").
Additional notes
- The order of degradation is informative: Downloads and Documents (TCC-gated) failed first, the project directory under
~/Desktopfailed last, and non-TCC directories never failed. That looks like TCC attribution decaying rather than a Seatbelt allowlist problem. - The auto-update at 20:43:46 partially restored access (Downloads came back, Documents didn't) — the opposite direction from #75374, where the update caused the denial. Whatever tccd caches per-process appears to be invalidated by the binary swap and then re-derived differently.
- The first denial preceded the auto-update by ~20 minutes, so the update was not the trigger here. #75374 notes denials beginning after
sandboxdrecycled; that may be the common trigger, and would be worth correlating.