Starting a session in Claude Desktop revokes filesystem access from already-running CLI sessions (macOS TCC identity collision)
Summary
On macOS, starting a Claude Code session inside Claude Desktop causes already-running Claude Code CLI sessions to lose read access to their own working tree, if that tree sits in a TCC-protected folder (~/Documents, ~/Desktop, ~/Downloads).
The running session does not fail cleanly. It keeps accepting work while every file read returns EPERM, and git reports fatal: Unable to read current working directory. In my case it burned most of a working session before the cause was found, because nothing in the output points at a permissions problem.
Environment
- macOS 26.5.1 (25F80), Apple silicon
- Claude Code CLI 2.1.227, host terminal has Full Disk Access
- Claude Desktop bundling Claude Code 2.1.229
- Repo at
/Users/<user>/Documents/GitHub/<repo>— inside the TCC-protected Documents folder
Reproduction
- Put a repo in
~/Documents. Give the host terminal Full Disk Access. - Start a Claude Code CLI session there. Confirm file reads work.
- Leave it running. Open Claude Desktop and start a Claude Code session in the same directory.
- Go back to the CLI session. Every read now fails:
cat package.json→Operation not permittedgit rev-parse --abbrev-ref HEAD→fatal: Unable to read current working directory: Operation not permitted- the file-read tool →
EPERM: operation not permitted, open '…'
- Close the Desktop-hosted session in the UI. CLI access returns immediately.
Step 5 restored access while the Desktop helper processes were still alive (verified with ps), so the trigger is the active session, not the process existing.
Evidence
Both binaries are the same TCC principal.
$ codesign -dv ".../Claude/claude-code/2.1.229/claude.app/Contents/MacOS/claude"
Identifier=com.anthropic.claude-code
TeamIdentifier=Q6L2SF6YDW
$ codesign -dv "~/.local/share/claude/ClaudeCode.app/Contents/MacOS/claude"
Identifier=com.anthropic.claude-code
TeamIdentifier=Q6L2SF6YDW
Claude Desktop launches Claude Code through Apple's responsibility-disclaiming helper.
/Applications/Claude.app/Contents/Helpers/disclaimer \
~/Library/Application Support/Claude/claude-code/2.1.229/claude.app/Contents/MacOS/claude …
Disclaiming stops the child inheriting Claude Desktop's TCC attribution, so it becomes responsible for itself — com.anthropic.claude-code, which has no Documents-folder grant. Being non-interactive, it cannot be prompted, so the request is denied.
The denial is scoped to the identity, not the process. The CLI session was covered by its host terminal's grant until the Desktop session created a self-responsible sibling sharing the same identifier. A TCC decision cached against com.anthropic.claude-code would explain why an unrelated, already-running process loses access. I could not read tccd's record to prove this last step — log show returns nothing usable without privileges and the TCC database itself needs Full Disk Access — so treat the caching mechanism as the best-fitting explanation rather than a measured fact. Everything above it is measured.
Ruled out:
- POSIX permissions, ACLs and file flags.
~/Documentsisdrwx------ <user> staffwith the stockgroup:everyone deny deleteACL, identical to~/Desktop, which stayed readable throughout. - Filesystem or mount fault.
staton the denied paths succeeds; onlyopen/readdirfail. That split is characteristic of TCC. - Path aliasing. The firmlink path
/System/Volumes/Data/Users/…is denied identically. - Claude Code's own sandbox. Retrying with the Bash sandbox override produced the same
EPERM. - The host terminal. The affected session ran under a third-party process supervisor, but per the reporter's own observation the same failure occurs with a plain terminal host, and the identity evidence above is host-independent.
Impact
A long-running session silently loses its working tree mid-task and carries on taking instructions. An agent in that state cannot read, test or commit, and the surfaced errors name neither permissions nor the real cause. Anyone keeping a repo in ~/Documents — a common default — hits this the first time they open a Desktop session alongside a CLI one.
Two separate defects
- Cross-session access revocation. A newly started session should not be able to remove filesystem access from sessions already running. Either let Claude Desktop's spawned Claude Code inherit Desktop's TCC responsibility instead of disclaiming to the shared identifier, or isolate the two so one session's evaluation cannot affect another's.
- No diagnosis on failure. When reads against the session's own working directory start returning
EPERM, Claude Code should say so once, clearly, and stop — rather than passing rawerrnoto the model and continuing. A one-line "the working directory has become unreadable; this is usually a macOS privacy grant" would have saved the entire investigation.
Note
Long-lived sessions outlive their binary: my session was still running 2.1.227, which no longer exists on disk (versions/ held 2.1.223, 2.1.232, 2.1.233, 2.1.235). Whatever fix lands should account for several versions sharing one daemon namespace and one signing identity at the same time.
Aside
I hear there are t-shirts. This one cost me a working session and a wrong diagnosis before the cause turned up, so I would like one.
3 Comments
Follow-up from the same machine, gathered from inside an affected session. Five things this occurrence adds: the direction reverses, the protected-folder control does not hold, the in-app folder-grant tools confirm grants they cannot deliver, a bad working directory poisons every
gitcall, and there is a third binary sharing the signing identity.Environment — same machine as the report, versions have moved on:
1. The direction reverses: the Desktop-hosted session lost access to its own working tree
The original repro has a Desktop session start and an already-running CLI session lose access. Here the disclaimed Desktop child is itself the one denied. A session resumed in Claude Desktop could not read the working tree it was resumed into. Process start times, local:
This agrees with the analysis in the report rather than contradicting it: the disclaimed child is responsible for itself as
com.anthropic.claude-code, that identity holds no protected-folder grant, and being non-interactive it cannot be prompted. It is worth recording because it means defect 1 is not simply "Desktop wins, CLI loses". The disclaimed child can be the one with nothing, and in that state the Desktop session is unusable for any repo in a protected folder — which is the harder failure, because the Desktop UI is where a user would expect a grant prompt to appear.2. All three protected folders are denied, not only Documents
The original ruled out ACLs partly on the grounds that
~/Desktopstayed readable. On this occurrence it does not:staton the denied directories still succeeds whileopenandreaddirfail — the same split as the report.$HOME,~/.claudeand/private/tmpstayed readable throughout. So the denial tracks the whole set of TCC protected-folder grants rather than any single folder, which I think strengthens the identity-level explanation: a per-folder ACL or mount fault would not take all three together and leave the unprotected paths alone.3. The in-app folder-grant tools report success while access stays denied
This is the one I would fix first, because it actively misleads.
From inside the affected Desktop session I called the folder-grant tools on the working directory. Both returned success:
Every read after that still returned
EPERM— through Bash, through the file-read tool, and with the Bash sandbox override applied. The grant layer is not reconciled with TCC, so it confirms a grant it cannot deliver.The cost is concrete: the success message is evidence, and it is wrong. Acting on it, I concluded the grant had landed and that the cause lay somewhere else, and spent the next several steps looking in the wrong place. The grant call should verify itself with a real read of the granted path and report the failure when the read fails.
4. Moving a session into a denied directory poisons every
gitinvocation, whatever its targetThe
change_directorycall succeeded and moved the session's working directory into the denied path. After that everygitcommand failed withincluding
git -C <a readable path>, because git callsgetcwdbefore it honours-C. Prefixingcd /restores enough for non-git commands to run.Minor next to the rest, but it points the same way as defect 2: the failure presents as a git or repository fault, and neither the message nor the exit code mentions permissions.
5. A third binary shares the signing identity, from a channel users do not know they installed
The report covers the Desktop-bundled and CLI binaries. There is at least one more. An editor extension speaking ACP — Zed's, in this case — pulls
@anthropic-ai/claude-agent-sdk-darwin-arm64from npm, and that binary is the same principal:One was running three and a half minutes before the affected session started. I am not claiming it as the trigger — I could not measure that, and I am not able to read
tccd's decisions any more than the original report could.The point is that the namespace in the Note is wider than two installs. Any editor extension shipping the agent SDK adds another instance of the same identity, on its own release cadence, installed transitively through npm, and the user has no reason to know it is there. A fix that reasons about "the CLI and the Desktop copy" will not cover it.
6. Version skew confirmed from the Desktop side
The running Desktop-hosted process is
.../claude-code/2.1.234/..., and 2.1.234 is no longer on disk — that directory now holds 2.1.235 alone. Same shape as the Note in the report, observed from the other host: the session outlived its own binary while still running.And on the aside in the original report: this is the second session it has cost me, so I will renew the t-shirt request. Happy to take any merch going.
I just hit this exact issue where claude deskto messes up the terminal where claude code is running's file system access.
cc 2.1.295 and claude desktop 2.31266.0