[BUG] macOS: auto-update deletes the running app bundle mid-session, revoking granted Full Disk Access until restart
Summary
On macOS, when Claude Code auto-updates while a session is running, the updater deletes the currently running version's application bundle from ~/Library/Application Support/Claude/claude-code/<version>/claude.app. The live process is left executing from a bundle that no longer exists on disk. macOS then stops honoring the Full Disk Access grant for that process, and all file operations on TCC-protected locations (Downloads, Documents, Desktop, iCloud Drive) begin failing with Operation not permitted — silently, mid-session, with no error identifying the cause. A full quit and relaunch restores access.
Environment
- Claude Code running under Claude Desktop
- Version transition observed: 2.1.181 → 2.1.187 (auto-update during an active session)
- macOS 27.0 (build 26A5368g), Apple Silicon (arm64)
- Inner bundle identifier:
com.anthropic.claude-code; host app:com.anthropic.claudefordesktop(TeamQ6L2SF6YDW) - Full Disk Access had been granted to Claude and working for ~120 days prior
Steps to reproduce
- Grant Claude Full Disk Access. Confirm a Claude Code session can read a protected folder (e.g.
ls ~/Downloads). - Keep that session running.
- Allow (or trigger) a Claude Code auto-update to a new version while the session is still open.
- The updater removes the previous
claude-code/<old-version>/bundle. - In the still-running session, run a file operation against a TCC-protected directory.
Actual behavior
ls ~/Downloads→Operation not permitted(EPERM). Same for~/Documents,~/Desktop, and iCloud Drive.- A non-protected directory (e.g. the project working directory) still reads fine.
- The failure persists even with the per-command shell sandbox disabled — i.e. it is OS-level TCC, not the Claude Code sandbox or Claude Code's own permission layer (which were fully permissive).
- The running process is executing from a bundle path that no longer exists on disk:
- Process path:
~/Library/Application Support/Claude/claude-code/2.1.181/claude.app/Contents/MacOS/claude - On disk: only
…/claude-code/2.1.187/exists (created at the time of the update); the2.1.181directory returns "No such file or directory".
Expected behavior
- An in-session auto-update must not delete the bundle of the running process.
- Permissions granted to the running process (Full Disk Access) must remain in effect for the life of that process.
Impact
- Silent, mid-session loss of file access on the user's primary folders; Read/Write tools begin failing.
- Naturally misattributed by the user to their own macOS privacy settings (Full Disk Access appears granted, yet access fails), sending them to debug the wrong thing.
- Any auto-update landing during an open session can reproduce it.
Suggested fixes
- Never delete the bundle of a running process. Defer removal of the old
claude-code/<version>/directory until no live process references it. - Stage updates; activate on restart. Download into a new path but keep the running version's bundle intact for the life of the process; switch over on next launch.
- Run from a stable path (e.g. a
…/claude-code/currentsymlink repointed atomically on restart) rather than executing directly from a version-stamped directory the updater later deletes. - Detect and surface the condition. If the running bundle's on-disk path disappears, show a clear notice ("Claude Code updated in the background; file access is suspended until you quit and reopen") instead of letting operations fail as unexplained
Operation not permitted.
Workaround
Fully quit Claude (⌘Q — not just closing the window) and reopen. A fresh process runs the intact, signed on-disk bundle and the existing Full Disk Access grant applies again.
Possibly related
- #69546 — macOS: a hardcoded CLI version path breaks on every version bump (same version-stamped-path fragility).
- #69140, #66997 — auto-update deleting user data on Windows (same "auto-update removes things it shouldn't" family).
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Good, precise report — the quit-and-relaunch recovery you found is right, but here's the root mechanism and, more usefully, how to prevent it entirely so it never bites you mid-session.
Why the grant evaporates. macOS TCC ties a Full Disk Access grant to the code-signed bundle at a specific path. When the updater deletes
claude-code/<old-version>/claude.appout from under the live process, that process is now executing from a bundle that no longer exists on disk. TCC can no longer match the running process back to the grant it issued (the path is gone, the inode is unlinked), so every subsequent access to a TCC-protected location (~/Downloads,~/Documents,~/Desktop, iCloud Drive) fails withOperation not permitted. A relaunch re-execs from the new bundle path, TCC re-matches, and access returns. So it's not a permissions reset — it's the grant being orphaned by the bundle deletion.Prevention (the real fix until Anthropic stages updates safely): stop auto-update from running during a session. Either:
DISABLE_AUTOUPDATER=1(orDISABLE_AUTOUPDATER=true) in the environment Claude Code launches with, and update deliberately between sessions; orThat removes the race entirely — the bundle is only swapped when nothing is executing from it.
Mid-session detection (so you don't waste an hour debugging "why did my file tools break?"): the signature is sudden
Operation not permittedon TCC-protected paths only, while non-protected paths (e.g. a repo under~/code) still work, and it appears right after a version bump. If file ops to~/Downloadsstart failing but/tmpand your project dir are fine, suspect this and relaunch before assuming a code or config problem.The deeper issue worth flagging to Anthropic: an updater that deletes the currently running version is unsafe on any platform — the standard pattern is to install the new version alongside and only retire the old bundle after the running process exits. The TCC revocation is just the most visible symptom of that.
Adding independent confirmation of this issue with additional data.
Environment
com.anthropic.claudefordesktop)2.1.202at~/Library/Application Support/Claude/claude-code/2.1.202/claude.app/Contents/MacOS/claude~/Documents(an Obsidian vault)Observed behavior
lsofon the target directory shows zero open file descriptors held by any process at failure time — ruling out a plain file lock and pointing to an OS-level TCC/sandbox denial instead.TCC.dbdirectly showsauth_value = 2(Allowed) forkTCCServiceSystemPolicyDocumentsFolderfor all relevant identities (com.anthropic.claudefordesktop,com.anthropic.claude-code, and the Terminal CLI's absolute path) — the grant looks intact in the database while access still fails in practice, consistent with this being silent and easy to misattribute to one's own privacy settings.~/Library/Application Support/Claude/claude-code/across updates, consistent with the versioned-bundle-path mechanism here and in #63671 / #74234 / #70094.Happy to share the full
ps aux/ TCC.db query output if useful.Still occurring on current builds, and I have two things to add that I think change the
diagnosis: the accepted workaround in this thread does not work, and the mechanism in
the comments above is wrong in a way that matters for the fix.
Desktop 1.40609.0, sidecar 2.1.246 → 2.1.247, macOS Darwin 25.5.0, Apple Silicon.
1.
DISABLE_AUTOUPDATER=1does not prevent thisOn the machine where this reproduced today, both are already in force:
It still happened. Those settings govern the desktop ShipIt updater, not the CCD
sidecar pruner that actually deletes the directory. Anyone applying the workaround in the
first comment is not protected.
2. It is not "the update swaps the bundle under a running process"
It is more specific, and more clearly a bug: on launch the app deliberately spawns new
work from the version it is about to delete. From
~/Library/Logs/Claude/main.log, thesame triple every time — spawn from X, then delete X, 2–3 seconds later:
cleanupOldVersionsForTarget()keeps{justInstalled, buildPinVersion, targetVersion}—there is no "has a live process" member. Its
.catch(... 'Could not remove old version %s (in use?)')guard reads like a Windowsassumption; on macOS unlinking a running executable succeeds silently, so it never fires.
Sixteen prunes are logged on this machine since 2026-05-12; only the four above bit,
because a prune only matters when a process is live from the doomed directory. A session
started 1–3 s after app launch lands squarely in the download window, so scheduled/background
tasks are the usual victims.
3. The failure is before signature validation, which is why TCC.db looks fine
The comments here say TCC "can no longer match the running process back to the grant."
It never gets that far.
tccdfails at client-identity construction:No signature is validated and no TCC.db row for any service is ever consulted — which
explains @Masaki0423's observation above that
auth_value = 2(Allowed) is present in theDB while access still fails. The reply carries no
auth_valueat all and the kernel deniesby default.
Two consequences:
sandboxdis logged askingkTCCServiceSystemPolicyAllFilesfirst on every failing request and getting the same
Code=2before any service-specificquery. So granting Full Disk Access cannot fix it — the FDA row is behind the same
broken attribution chain. Worth stating explicitly, because FDA is the reflexive fix and
it costs people time.
Contents/Helpers/disclaimer(posix_spawn disclaim), so the sidecar is the responsible process for every Bash/Python
descendant. One poisoned pid takes down everything the session shells out to.
4. Minimal reproducer — 30 seconds, no Claude involved, no update needed
The unlink is the single manipulated variable. This isolates the mechanism without waiting
for an auto-update.
5. Confirming an instance in the field
Why this is worth un-staling
The failure is silent and mimics something else. It broke an automated daily task here four
times in twelve days, and each time it presented as a cloud-storage sync problem — because
~/Library/CloudStoragewas the visible casualty while~/Documentsand~/Downloadswereequally denied.
stat()keeps working whileopen()/readdir()fail, which reads like filecorruption rather than a permissions problem.
--resumemakes it materially worse: the app kills the old session, spawns a new process fromthe doomed version, and resumes into it, so the transcript reads as continuous across the
process boundary. There is no visible seam where access was lost, and the successful file
writes from minutes earlier are from a different process — which sends you looking for a
mid-session revocation that never happened.
Retrying cannot recover it: the denial is cached in-kernel per pid and the running vnode has no
path. Only a brand-new process helps.
Suggested fixes, narrower than the ones above
flight — wait for the new bundle. That alone closes the race in the logs above.
Happy to supply the full
tccd/sandboxdunified-log extracts for any of the four dates.