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

Status Open
Reported on v2.1.232
Maintainer reply None cached
Activity 0 comments · opened Aug 16, 2026

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 by git 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 sandbox key and no denyRead in 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 ~/DocumentsOperation 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:

  1. Missing grant. System TCC db: kTCCServiceSystemPolicyAllFiles | …/claude-code/bin/claude.exe | 2 | 2026-05-27 07:20:29, and the same for com.apple.Terminal. Both hold FDA.
  2. 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, and codesign --verify -R <req> claude.exe returns satisfied — against the post-update binary.
  3. 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 string System Policy does 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?

  1. 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.
  2. 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.
  3. 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 ~/Desktop failed 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 sandboxd recycled; that may be the common trigger, and would be worth correlating.

View original on GitHub ↗