Worktree-isolated sessions still refuse Bash commands that never run git (2.1.270)
Related issues
This report adds 2.1.270 reproductions to a problem already open upstream. The
issues below were filed before, or alongside, the 2.1.257 and 2.1.259 fixes, so
the shapes still refused after those fixes are worth recording separately.
| Issue | Title | Version it reports |
| --- | --- | --- |
| #90293 | Worktree-isolated sessions: benign read-only command shapes refused as "too complex to verify"; suggested remediation cannot apply to loops | 2.1.243 |
| #90307 | Worktree-isolated background sessions: command shape verifier refuses safe read-only commands at scale | 2.1.243 |
| #93193 | Bash sandbox for isolation:worktree dispatched agents false-blocks on the substring "git" anywhere in the command | not stated |
On 2.1.270, the substring rule described in #93193 no longer holds for quoted
arguments: printf '%s\n' 'never run git branch -D' is allowed. It still holds
for heredoc text fed to an unrecognized program, as reproduction 3 shows.
Summary
When a Claude Code session runs inside a git worktree, Claude Code checks each
Bash command and refuses any command it cannot prove stays inside that worktree.
On 2.1.270 the check still refuses commands that contain no git operation. The
2.1.257 and 2.1.259 changelogs describe fixes for this class of refusal. Loops
now pass, but six other shapes are still refused, and each one has a control
that shows the refused command is harmless.
On one machine, the refusal fired 181 times in one night across four sessions
on 2.1.269, and 120 more times the next night across seven sessions. It was the
largest class of blocked commands both nights.
Environment
The following table describes the machine the reproductions ran on.
| Item | Value |
| --- | --- |
| Claude Code | 2.1.270 |
| Operating system | macOS, Darwin 24.6.0 (BSD userland) |
| Shell | zsh |
| Session working directory | a linked worktree, <repo>/.claude/worktrees/<name> |
In the reproductions, helper is any executable on PATH that is not git and
that Claude Code does not recognize, such as a small personal shell script.
Reproductions
Each reproduction below is one Bash tool call, made from inside the
worktree-isolated session. Every refused command is followed by a control that
Claude Code allowed. The quoted refusal text is verbatim except for the path.
1. A program Claude Code does not recognize, given $PWD
helper --raw "$PWD"
This session is isolated in the worktree <worktree>, but this command runs helper with a value computed at runtime (the variable PWD) inside a construct too complex to verify, so what it runs cannot be shown not to be git.
Allowed controls: the same command with the path spelled out;ls -d "$PWD"; wc -l "$PWD/CLAUDE.md".
$PWD in a plain command with no cd is the session's own working directory,
which is the worktree. It is the one value the check could resolve with
certainty. Note also that the command is a single plain command, yet the message
calls it a "construct too complex to verify".
2. sed reading a path built from $PWD
sed -n 1p "$PWD/CLAUDE.md"
... but this command runs sed with a value computed at runtime (the variable PWD) where an option may stand (a value that is not double-quoted, or whose first character is matched or computed rather than spelled out, may begin with -; put -- before it) in a plain command, so what it runs cannot be shown not to be git.
Allowed control: the same command with the path spelled out.
The value is double-quoted, and $PWD is always an absolute path, so the word
begins with / and cannot be read as an option. The suggested fix also does not
work on macOS. sed -n 1p -- "$PWD/CLAUDE.md" passes the check, but BSD sed
stops reading options at the script 1p, treats -- as a file name, and
prints sed: --: No such file or directory.
3. A heredoc whose text mentions git, fed to an unrecognized program
helper --raw <<'EOF'
a reminder that we must never run git branch -D on a shared branch
EOF
... but this command feeds helper text naming git in a plain command, which cannot be shown to stay inside the worktree.
Allowed controls: the same heredoc fed to cat; a heredoc with no git words fed
to helper; the same sentence passed to printf as a quoted argument.
The heredoc body is quoted data. The refusal happens only when the receiving
program is one Claude Code does not recognize, which suggests the check assumes
such a program might execute its standard input.
4. Command substitution in a read-only pipeline
strings "$(command -v claude)" | grep -c isolated
... but this command runs strings with a value computed at runtime (command output) inside a construct too complex to verify, so what it runs cannot be shown not to be git.
strings and grep only read. The substituted value becomes a file argument tostrings, not a program name.
5. time wrapping a command whose arguments include the word find
/usr/bin/time -p python3 -c 'print(1)' find --name foo
... but this command runs time with the text find in a plain command, so what it runs cannot be shown not to be git.
Allowed controls: the same command without /usr/bin/time; the same command
with time but without the word find.
The program time runs is python3. The word find is an argument to the
Python script, not a command. The check appears to re-read every word after the
wrapper as if it could start a command.
6. time wrapping a command with a quoted regular expression
/usr/bin/time -p echo --grep 'a.*b'
... but this command hands time the text a.*b, which runs a command whose name is computed at runtime in a plain command, so it cannot be shown not to be git.
Allowed controls: echo --grep 'a.*b' without time; /usr/bin/time -p echo.
hello
'a.*b' is single-quoted, so the shell never expands it, and it is an argument
to echo, not a program name.
What now passes
A for loop that feeds a file to a program on standard input is allowed, both
for wc and for an unrecognized helper:
for n in 1; do helper --raw < /abs/path/file; done
This matches the 2.1.259 changelog entry about loops.
Expected behavior
A command that cannot run git should run. Four narrower rules would remove every
refusal above without weakening the protection the check exists for:
- Resolve
$PWD. In a plain command with no precedingcd,$PWDis the
session's working directory. A value that starts with it stays inside the
worktree.
- Treat a double-quoted word that starts with
$PWDas a non-option. An
absolute path cannot begin with -.
- Parse wrappers such as
timethe way the shell does. The first
non-option word after the wrapper is the program; every later word is that
program's argument. A single-quoted word is literal text, never a computed
program name.
- Only inspect heredoc text when the receiving program can execute it, such
as sh, bash, zsh, python3, or xargs, or offer a setting that lists
programs known not to run git.
Separately, the put -- before it hint should not be offered for sed on
macOS, where it breaks the command.
Why this matters
Every refusal above has a trivial walk-around: spell the path out, write the
text to a file and redirect it, or split the pipeline. A session that is refused
takes the longer route and performs the same action. The check therefore adds
steps and tokens without preventing anything, and on busy days it is the most
common reason a command is blocked.