[Bug] Sandbox write-deny on `.claude/.cc-writes` prevents git worktree removal after ExitWorktree
Bug Description
# Sandbox write-deny on .claude/.cc-writes makes linked git worktrees unremovable (even via ExitWorktree remove), leaving a .cc-writes stub
## Environment
- Claude Code: 2.1.216
- OS: macOS 26.5.2 (Darwin 25.5.0), Seatbelt sandbox (sandbox.enabled: true, auto-allow)
- git: 2.53.0
- Worktrees located outside the repo (a sibling dir), created via a WorktreeCreate/WorktreeRemove hook that runs git worktree add/git worktree remove. EnterWorktree/ExitWorktree are used to move the session into/out of the worktree.
## Summary
When the sandbox is enabled and the session has entered a git worktree (so the worktree became the session's project root), Claude Code creates <worktree>/.claude/.cc-writes/. That directory is write-denied by the sandbox (it falls under the .claude self-protection). The deny persists after ExitWorktree. Consequently any later attempt to remove the worktree via a sandboxed command fails:
``
error: failed to delete '<...>/worktrees/<name>': Operation not permitted
error: failed to delete '.git/worktrees/<name>': Operation not permitted
`
git deletes everything it can and stops on .cc-writes, leaving an orphaned stub whose only content is <name>/.claude/.cc-writes/ (an empty, mode-700 dir).
Crucially, **Claude Code's own ExitWorktree({action: "remove"}) also fails to remove the worktree** for the same reason, so this is not specific to a user's custom removal script — the native teardown path hits the same wall.
Running the identical git worktree remove **outside** the sandbox (dangerouslyDisableSandbox) succeeds, confirming the blocker is the sandbox deny, not a file flag or permission (the .cc-writes dir has no uchg/schg flags; its only xattr is com.apple.provenance).
## Reproduction
1. Enable the sandbox (sandbox.enabled: true, auto-allow).
2. EnterWorktree into a new linked worktree.
3. Make any file edit with the Edit/Write tool while in the worktree — this creates <worktree>/.claude/.cc-writes/.
4. ExitWorktree({action: "keep"}) back to the main checkout.
5. From the main checkout, run git worktree remove --force <worktree> as a sandboxed Bash command.
→ fails with Operation not permitted on the worktree body and .git/worktrees/<name>.
Also: repeat 1–3, then ExitWorktree({action: "remove", discard_changes: true}) directly (as the worktree's owner) → **also fails** to remove ("could not remove it").
Direct probes confirming the mechanism:
- While entered, touch <worktree>/.claude/.cc-writes/x → Operation not permitted.
- After ExitWorktree(keep), the same touch from the main checkout still → Operation not permitted (deny persists).
- A worktree that was **never entered** has a fully deletable .cc-writes (the deny is scoped to session-registered roots, i.e. exact paths, not a **/.claude/.cc-writes glob).
## Impact
- Worktree removal fails and leaves an un-deletable stub (<name>/.claude/.cc-writes/) that accumulates across sessions.
- The only reliable cleanup is to run the removal unsandboxed.
- Because the native ExitWorktree(remove) path is affected too, there is no in-sandbox way to tear down a worktree the session has entered.
## Suspected root cause
.claude/.cc-writes (the Edit/Write staging area, undocumented) is write-protected as part of the .claude self-protection so sandboxed shell commands can't tamper with the harness's write staging. The protection is registered for each session-root .claude (including entered worktrees) but is **not lifted when the worktree is being torn down**, and .cc-writes is never cleaned up on exit. This is the same family as:
- #76995 — empty .claude/.cc-writes/ directories are not cleaned up after execution.
- #78818 — the sandbox materializes/persists a protected-path artifact (.git/config.lock) in linked worktrees and then breaks later git operations.
## Suggested fixes (any one)
1. On ExitWorktree/worktree teardown, drop the worktree's .cc-writes deny (and/or remove the .cc-writes dir) before/so that the worktree can be deleted.
2. Have ExitWorktree({action: "remove"}) perform the deletion with the harness's own (unsandboxed) privileges, the same way it can create/write .cc-writes.
3. Don't place .cc-writes` inside the worktree at all — stage writes in a stable per-session location outside the working tree (e.g. under the session temp dir), so a worktree never carries an un-removable artifact.
4. When a denied path does not exist / is being removed, deny cleanly without leaving a persistent artifact (mirrors the suggestion in #78818).
Environment Info
- Platform: darwin
- Terminal: vscode
- Version: 2.1.216
- Feedback ID: 26a80654-b233-4724-acf5-9436fa8f1ac7
Errors
[]This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗