[Bug] Worktree isolation guard false positive: substring match on "git" blocks commands

Status Open
Reported on v2.1.295
Maintainer reply None cached
Activity 3 comments · opened Oct 9, 2026

Bug Description
Worktree-isolation guard has false positives from naive substring matching on "git". In a worktree-isolated session, any Bash command containing the substring "git" anywhere (even inside a word) is refused if it also contains a shell expansion. Repro: mvn -Dmaven.gitcommitid.skip runs, but mvn -Dmaven.gitcommitid.skip; echo exit=$? is refused with "names git in a form too complex to verify that it stays inside the worktree". Likewise grep -n 'git-commit-id' ~/.m2/repository/io/airlift/airbase/*/airbase-*.pom is refused: the quoted grep pattern is treated as a git invocation and the glob as git's argument. Neither command runs git. Real-world impact: a plain Maven build (mvn ... -Dmaven.gitcommitid.skip > log 2>&1; echo exit=$?) was blocked, and the subagent worked around it with a wrapper script, which defeats the purpose of the guard. Please parse the command and match git only in command position (the program actually being executed), not as a substring of arguments, flags or patterns.

Environment Info

  • Platform: darwin
  • Terminal: ghostty
  • Version: 2.1.295
  • Feedback ID: 3b707d92-8455-4893-aeea-c72f32c318ae

View original on GitHub ↗

3 Comments

adam-stone-zocdoc · 5 days ago

Same on 2.1.296. A common trigger is repos under ~/git/: every absolute path contains "git", so any loop, $VAR, $PWD, or ; echo exit=$? alongside a path gets refused. Examples that never run git:

for s in a b c; do DAGSTER_HOME=~/git/repo/.claude/worktrees/wt mytool find --substring $s; done
cd ~/git/repo/.claude/worktrees/wt && DAGSTER_HOME=$PWD mytool test > /tmp/x.log 2>&1; echo exit=$?

evoskamp · 3 days ago

Same bug on 2.1.295 (macOS), hit at scale. In one orchestrated session, 62 isolation: worktree subagents had Bash calls refused with "names git in a form too complex to verify that it stays inside the worktree". Of 77 refused commands, 43 ran no git at all. The repo has a plugins/git-tools/ directory and a git-branch-sync script, so "git" appears in the paths.

Refused commands that never run git:

shellcheck plugins/git-tools/bin/git-branch-sync; echo rc=$?
/bin/bash --version | head -1; /bin/bash plugins/git-tools/test/git-branch-sync-test.sh > .claude/tmp/out.txt; echo rc=$?
mkdir -p .claude/tmp/fix && cat > .claude/tmp/fix/msg.txt <<'EOF'
State each git-branch-sync exit condition in one place
EOF

The last one is a commit-message file. The word only appears in the heredoc's body.

The refusal comes from the Bash tool itself, after the permission layers have already allowed the call. For one refused call, the transcript record shows:

  • the PreToolUse:Bash hook exited 0 and returned {"hookSpecificOutput":{"hookEventName":"PreToolUse"}}, so it abstained;
  • permissionDecision is {"decision":"accept","source":"config","reasonType":"classifier"}, so the auto-mode classifier accepted it;
  • toolUseResult is "Error: This agent is isolated in the worktree …, but this command names git in a form too complex to verify …".

No hook or permission rule can allow these commands back. Every agent working in a repo whose paths contain "git" pays for it in retries and reworded commands.

haoblackj · 1 day ago

Another data point, from a retrospective count over my local Claude Code transcripts (2026-07-26 to 2026-10-12; 544 interactive sessions and 2,639 subagent transcripts; tool calls deduplicated by tool_use.id):

  • 1,698 tool calls were refused by the worktree-isolation guard, out of 137,149 tool calls (1.2%). That is 30.5% of all tool errors in the period.
  • 349 of the refusals said "names git in a form too complex to verify that it stays inside the worktree". In 253 of those, the command contained no git or gh word at all. It only contained the substring github, because the repositories are checked out under a .../github.com/<owner>/<repo> layout, so every absolute path matches.
  • This false positive first appeared in the week of 2026-09-07 and peaked at 135 refusals in the week of 2026-09-28.
  • Most refusals cost one extra round trip: 1,319 of 1,481 runs of consecutive refusals were a single refusal, and the next call succeeded 95% of the time. In at least one session the agent got past it by splitting the path string, which defeats the purpose of the guard.
  • The guard also catches real problems: 96 refusals were git aimed at the shared checkout (git -C 47, cd 23, working directory resolving there 26). So I would rather see git matched only in command position, as the original report suggests, than have the guard disabled.