[BUG] Auto-updater deletes old version dir while sessions still run from it; macOS TCC then denies external-volume access (EPERM)
Environment
- Claude Code desktop app on macOS (Darwin 25.5.0), claude-code 2.1.197 -> 2.1.202
- Project located on an external APFS volume (
/Volumes/<ssd>)
What happened
- A long-lived session was started under claude-code 2.1.197 (desktop app).
- The next morning the app auto-updated.
~/Library/Logs/Claude/main.logshows[CCD] Removing old version: 2.1.197- the version directory under~/Library/Application Support/Claude/claude-code/was deleted while the session's process was still running from that binary. - The session kept working for ~4h (it even accessed the external volume successfully 1h after the update - TCC/sandboxd approval caches were still warm).
- After
sandboxdrecycled (new pid observed in the unified log), every access from that session's process tree to the external volume failed with EPERM:
- Bash children:
ls /Volumes/<ssd>: Operation not permitted,cat <file>: Operation not permitted - the native Read tool:
EPERM: operation not permitted, open '...' shell-init: error retrieving current directory: getcwd: cannot access parent directories: Operation not permitted(session cwd was on the volume)- signature:
stat/metadata still worked;open/readdir/writedenied./tmp,$HOMEand network unaffected.
- New sessions under 2.1.202 worked fine at the same time, on the same paths.
Root cause (from the unified log)
With the old binary gone from disk, tccd can no longer validate the responsible process's code signature. TCC attribution for kTCCServiceSystemPolicyRemovableVolumes falls back to the accessing tool itself (e.g. /usr/bin/git -> com.apple.dt.xcode_select.tool-shim-public), which has no TCC record -> Unknown -> sandboxd denies. During the broken window the tccd log shows only these shim preflights, with no follow-up request attributed to com.anthropic.claude-code; as soon as a new 2.1.202 session starts, com.anthropic.claude-code requests reappear with authValue=2 (allowed).
Why this is worse than the ENOENT variant
Related: #47253 (auto-updater deletes running binary -> ENOENT on spawn), closed as duplicate of #18019 (Homebrew variant, closed stale). This TCC variant does not crash the session: it half-works (API, /tmp, $HOME all fine) and presents as filesystem corruption, ACLs, or a stuck lock on one specific path. It cost us a full diagnostic session, and subagents spawned to "independently reproduce" the failure all inherited the same broken process tree, so the misdiagnosis was reinforced.
Expected behavior
Keep old version directories until no running session uses them (refcount + GC on session exit), or restart/migrate running sessions as part of the update.
Workaround
Start a new session (it runs from the current binary). No system permission changes needed.