Worktree-isolated sessions still refuse Bash commands that never run git (2.1.270)

Status Open
Reported on v2.1.270
Maintainer reply None cached
Activity 0 comments · opened Sep 13, 2026

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 to
strings, 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:

  1. Resolve $PWD. In a plain command with no preceding cd, $PWD is the

session's working directory. A value that starts with it stays inside the
worktree.

  1. Treat a double-quoted word that starts with $PWD as a non-option. An

absolute path cannot begin with -.

  1. Parse wrappers such as time the 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.

  1. 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.

View original on GitHub ↗