[Bug] Sandbox write-deny on `.claude/.cc-writes` prevents git worktree removal after ExitWorktree

Status Closed — not planned
Reported on v2.1.216
Maintainer reply ✓ Yes — bcherny
Activity 4 comments · opened Jul 21, 2026 · closed Sep 15, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

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

[]

View original on GitHub ↗

4 Comments

bcherny collaborator · 2 months ago

Thanks for the detailed report. I reproduced this on 2.1.233 (macOS, sandbox enabled): after entering a worktree, making an edit, and exiting with keep, a sandboxed git worktree remove --force <path> from the main checkout fails with Operation not permitted on the worktree and .git/worktrees/<name>, leaving behind an empty <worktree>/.claude/.cc-writes/ directory. Writes to that directory are still denied after exiting the worktree, which is what blocks the removal.

One thing I could not reproduce on 2.1.233: ExitWorktree({action: "remove", discard_changes: true}) on a standard (git-managed, non-hook) worktree removed it successfully, since that removal runs outside the sandbox. If your WorktreeRemove hook runs git worktree remove without --force, git will refuse because of the untracked .claude/.cc-writes/ directory — worth checking on your side.

Confirmed as a bug: the write protection on the worktree's staging directory should be released when the session leaves the worktree, and the empty directory should not be left behind. We are tracking a fix.

🤖 Generated with Claude Code

github-actions[bot] · 1 month ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

Arkemlar · 2 days ago

+1 here, not fixed. This should not be closed.

marcindulak · 2 days ago

This issue was incorrectly closed by the github-actions bot, despite having reproduction steps and Claude version. This bot behavior is tracked in https://github.com/anthropics/claude-code/issues/87647. Consider upvoting that issue for visibilty, maybe the bot behavior gets fixed.