[BUG] Worktree isolation hard-blocks ALL `git -C <main-checkout>` calls — even read-only, even after a PreToolUse hook explicitly approves it

Status Open
Reported on v2.1.222
Maintainer reply None cached
Activity 5 comments · opened Aug 5, 2026

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

  1. Create a worktree-isolated session, e.g. via the EnterWorktree tool or claude --worktree test.
  2. Inside the worktree, make a commit on the worktree branch.
  3. Run a read-only command that redirects to the main checkout, e.g.:

git -C /absolute/path/to/main/checkout status --short

  1. (Optional, makes the bug worse) Have a PreToolUse hook that flags cross-directory Bash access. It will prompt for approval. Approve it.
  2. Observe the command is refused anyway, approval or not.
  3. 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, or GIT_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.

View original on GitHub ↗

3 Comments

fluidsonic · 24 days ago

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 --version 2.1.223):

MERGE_BASE=$(git merge-base HEAD main)
echo "commit_count=$(git rev-list --count $MERGE_BASE..HEAD)"

Refused with:

This session is isolated in the worktree <path>, but this command is too complex to verify that it stays inside the worktree; break it into plain, separate commands. Refusing to run it — a worktree-isolated session's git operations must target its own worktree. Run the equivalent from <path> without the redirect.

There's no -C, no --git-dir, no cross-tree redirect of any kind — main resolves 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 earlier git merge-base command substitution is reused in a later statement. Bisected it down to:

  • VAR=$(git ...) followed by reusing $VAR as a bare/standalone argument (quoted or unquoted) to any later command → refused
  • VAR=$(git ...) followed by reusing $VAR inside a second $(...) in a later statement → refused
  • VAR=$(git ...) followed by echo "prefix=$VAR" (variable embedded in a literal-prefixed string, no second substitution) → fine
  • The exact same two-line block, run from a subagent spawned inside the worktree session rather than the coordinator → refused identically, confirming the "isolation now applies to ... every session type" changelog note extends this static verifier to subagents too.

Reproduces 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 -C case 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.

arvenil · 19 days ago

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?

KyleSlusser · 16 days ago

+1

Showing cached comments. Read the full discussion on GitHub ↗