[BUG] Worktree isolation hard-blocks ALL `git -C <main-checkout>` calls — even read-only, even after a PreToolUse hook explicitly approves it
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
2.1.222's changelog claims this fix targets "destructive git commands against the main checkout." It doesn't. It hard-blocks any git command that redirects to the main checkout via -C from a worktree-isolated session, including plain read-only commands like git -C <main> status. There is no setting, no permission rule, and no escape hatch to allow it, even for a repo the user owns and is deliberately targeting.
This breaks a completely standard git-worktree pattern that Claude Code's own docs describe as safe: "git commands in a worktree write to the main repository's shared .git directory, and sandboxing allows those writes." That line is now false. Our team's entire "isolate work in a worktree, then fast-forward-merge the branch into local main from that same session" finishing step is dead, not degraded, not prompted more, just unconditionally refused.
The part that actually makes this a bug and not just a debatable policy: we have a PreToolUse hook that flags exactly this kind of cross directory Bash command and asks the user to approve it. The user approved it. Claude Code refused to run the command anyway, with a hardcoded message that ignores the approval entirely. That means "approve" doesn't mean anything for this class of commands: the permission system asks for consent it then discards. A prompt the user can say yes to should be honored once they say yes, not silently overridden by a hard block underneath it.
What Should Happen?
git -C <path-to-main-checkout> (and any git command targeting the main checkout) from a worktree-isolated session should go through the normal permission system: prompt, and if the user approves, run it. It should not be hard-refused underneath an approval that already happened.
We need a supported, documented way to finish a worktree session by merging its branch into local main. Right now there is none: git -C <main> merge --ff-only <branch> is exactly that operation, and it is unconditionally blocked with no alternative path offered. This needs to exist regardless of whether the -C restriction itself gets relaxed.
If this is meant to be a genuine, non-overridable security boundary, it has to be documented. The worktree page currently says the opposite: "git commands in a worktree write to the main repository's shared .git directory, and sandboxing allows those writes."
Error Messages/Logs
This session is isolated in the worktree /path/to/repo/.claude/worktrees/<name>, but this command redirects git to the shared checkout via -C. Refusing to run it — a worktree-isolated session's git operations must target its own worktree. Run the equivalent from /path/to/repo/.claude/worktrees/<name> without the redirect.
Steps to Reproduce
- Create a worktree-isolated session, e.g. via the
EnterWorktreetool orclaude --worktree test. - Inside the worktree, make a commit on the worktree branch.
- Run a read-only command that redirects to the main checkout, e.g.:
git -C /absolute/path/to/main/checkout status --short
- (Optional, makes the bug worse) Have a PreToolUse hook that flags cross-directory Bash access. It will prompt for approval. Approve it.
- Observe the command is refused anyway, approval or not.
- Repeat with the actual merge command:
git -C /absolute/path/to/main/checkout merge --ff-only <worktree-branch>
Same refusal.
Claude Model
Not sure / Multiple models
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.221
Claude Code Version
2.1.222 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
We do not want to exit the worktree with action: "keep"!
- Merging may fail because main has moved in a parallel session, which means resuming worktree again, rebasing, exiting again, in a loop.
- We may want to continue work (mid-work merge into local main is not uncommon).
- We want to avoid having to delete the worktree manually afterward.
Relevant changelog entries:
- 2.1.222: "Fixed worktree-isolated sessions and their subagents being able to run destructive git commands against the main checkout; isolation now applies to file edits and Bash in every session type"
- 2.1.216: "Fixed worktree-isolated subagents redirecting git into the shared checkout via
git -C,--git-dir, orGIT_DIR/GIT_WORK_TREE"
Docs page currently contradicting the observed behavior:
https://code.claude.com/docs/en/worktrees, "What worktrees share with the main checkout" section, first bullet.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Follow-up: this isn't limited to
-C/cross-tree redirects. There's a second, narrower trigger that never touches the main checkout at all — purely within the worktree's own checkout — and it's just as unconditionally blocked, no escape hatch, same "too complex to verify" message family.Repro (from inside a worktree-isolated session,
claude --version2.1.223):Refused with:
There's no
-C, no--git-dir, no cross-tree redirect of any kind —mainresolves as a purely local ref inside the worktree's own checkout (worktrees share the object database, so this is completely in-bounds). The refusal fires only because a variable populated by an earliergit merge-basecommand substitution is reused in a later statement. Bisected it down to:VAR=$(git ...)followed by reusing$VARas a bare/standalone argument (quoted or unquoted) to any later command → refusedVAR=$(git ...)followed by reusing$VARinside a second$(...)in a later statement → refusedVAR=$(git ...)followed byecho "prefix=$VAR"(variable embedded in a literal-prefixed string, no second substitution) → fineReproduces on 2.1.223 too. Same root cause as this issue: the verifier fails closed on any compound command it can't statically prove stays in-bounds, even when the command provably never leaves the worktree (no cross-tree redirect, just local refs and a shell variable). That's a second class of false positive beyond the
-Ccase already reported here, worth fixing alongside it since it blocks plain diff/rev-list bookkeeping that's clearly harmless — the kind of thing any git-based finishing workflow does constantly.I'm running claude in a sbx sandbox with bypass permission - I don't care what claude does inside of sandbox - how can I disable this blocker? Is there ULTRA BYPASS PERMISSION?
+1