Summary
In 2.1.222, a worktree-isolated session refuses almost every Bash command 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.
The trigger is any environment-variable expansion the guard cannot resolve — not command complexity, not git, not redirects. echo "$TERM" is refused.
This worked fine in 2.1.221. In 2.1.222 the guard was widened from worktree-isolated agents to the worktree-isolated session itself, so it now intercepts the user's own commands.
Repro
- In a git repo:
claude --worktree testwt
- Ask Claude to run:
echo "$TERM"
Expected: prints the value of TERM.
Actual: refused with the message above.
The trigger is env-var expansion
Measured in a live 2.1.222 isolated session:
| Command |
Result |
echo a && echo b |
pass |
false || echo b |
pass |
echo "x$(pwd)x" |
pass |
[ -f README.md ] && echo yes |
pass |
echo "$HOME" |
pass |
echo "$TERM" |
refused |
echo "$SOME_UNSET_VAR" |
refused |
[ "$CLAUDE_AUTONOMOUS" = "1" ] |
refused |
&&, ||, $(...), and [ ... ] all pass. $HOME resolves, but $TERM — which is set — is refused. So this is a narrow resolution allowlist, not a set-vs-unset check. Any variable outside it pushes the parse to the kind !== "simple" branch that emits the refusal.
Two secondary problems with the message itself:
- It says "without the redirect" even when the command contains no redirect. This sent me looking for a redirect that did not exist.
- It says "git operations must target its own worktree" for
echo "$TERM", which involves no git and no paths.
What changed between 2.1.221 and 2.1.222
The guard string is present in 2.1.220, 2.1.221 and 2.1.222 alike — the guard is not new. Its scope is. Extracted from each binary:
// 2.1.221
let n=(f)=>`This agent is isolated in the worktree ${r} … a worktree-isolated agent's git operations…`
// 2.1.222
let {noun:n,possessive:o}=Zcn(r), i=(h)=>`${n} is isolated in the worktree ${r} … ${o} git operations…`
2.1.222 parameterizes the noun so the same guard covers sessions:
$ grep -ac "worktree-isolated session" ~/.local/share/claude/versions/2.1.221
0
$ grep -ac "worktree-isolated session" ~/.local/share/claude/versions/2.1.222
2
The observed message says "This session is isolated" — wording 2.1.221 cannot produce.
Environment
|
|
| Claude Code |
2.1.222 (2.1.221 unaffected) |
| Platform |
macOS 26.5.2 (25F84), arm64 |
| Node |
v22.23.2 |
| Shell |
zsh |
| Install method |
native, autoUpdates: false |
Impact
We use one worktree per terminal tab, launched via claude --worktree <name>. On 2.1.222 that makes the session largely unusable:
- Slash commands with a
! bash preamble die at expansion, before running anything. Our session-bootstrap command contains ![ "$CLAUDE_AUTONOMOUS" = "1" ] && echo "ON" || echo "off"`` and now fails outright.
- 22 of our 38 project skills use shell variables in their bash blocks and break mid-run — including every quality gate we depend on (verify, review, commit-push, promote).
- The only skills that survive are those delegating to
npx tsx <script> with no inline shell variables.
There appears to be no opt-out. No isolation-disable setting exists in the binary — worktreeIsolation, disableWorktreeIsolation, isolationEnabled all return 0 matches, and the only worktree.* keys are lock, baseRef, canonical, sparsePaths, path. ExitWorktree does not help either: it only unwinds worktrees created by the EnterWorktree tool, not isolation established by the --worktree launch flag.
Downgrading does not stick
The obvious mitigation — repoint ~/.local/bin/claude at 2.1.221 — is silently undone. Launching claude re-points the symlink to the newest version present in ~/.local/share/claude/versions/:
$ ln -sfn ~/.local/share/claude/versions/2.1.221 ~/.local/bin/claude
$ ls -l ~/.local/bin/claude
… 10:11 … -> /Users/…/versions/2.1.221
# 8 minutes later, after starting one new session:
$ ls -l ~/.local/bin/claude
… 10:19 … -> /Users/…/versions/2.1.222
This happens with "autoUpdates": false and "installMethod": "native". The new session launched on 2.1.222 and hit the bug again. The only workaround we found was physically moving the 2.1.222 binary out of the versions directory.
If auto-updates are disabled, launching should not rewrite the version symlink.
What I'd like to see
- Resolve environment variables from the session's real environment before the "simple" classification, instead of refusing on any non-allowlisted variable.
- Scope the refusal to commands that actually touch git or filesystem paths.
echo "$TERM" cannot escape a worktree.
- Provide a documented opt-out setting for the session-level guard.
- Fix the message: drop "without the redirect" when there is no redirect, and stop describing non-git commands as git operations.
- Release-note behavioral changes like this. The guard's scope changing from "agent" to "session" is a breaking change for anyone using
--worktree, and it arrived in a patch release with no signal.
Summary
In 2.1.222, a worktree-isolated session refuses almost every Bash command with:
The trigger is any environment-variable expansion the guard cannot resolve — not command complexity, not git, not redirects.
echo "$TERM"is refused.This worked fine in 2.1.221. In 2.1.222 the guard was widened from worktree-isolated agents to the worktree-isolated session itself, so it now intercepts the user's own commands.
Repro
claude --worktree testwtecho "$TERM"Expected: prints the value of
TERM.Actual: refused with the message above.
The trigger is env-var expansion
Measured in a live 2.1.222 isolated session:
echo a && echo bfalse || echo becho "x$(pwd)x"[ -f README.md ] && echo yesecho "$HOME"echo "$TERM"echo "$SOME_UNSET_VAR"[ "$CLAUDE_AUTONOMOUS" = "1" ]&&,||,$(...), and[ ... ]all pass.$HOMEresolves, but$TERM— which is set — is refused. So this is a narrow resolution allowlist, not a set-vs-unset check. Any variable outside it pushes the parse to thekind !== "simple"branch that emits the refusal.Two secondary problems with the message itself:
echo "$TERM", which involves no git and no paths.What changed between 2.1.221 and 2.1.222
The guard string is present in 2.1.220, 2.1.221 and 2.1.222 alike — the guard is not new. Its scope is. Extracted from each binary:
2.1.222 parameterizes the noun so the same guard covers sessions:
The observed message says "This session is isolated" — wording 2.1.221 cannot produce.
Environment
autoUpdates: falseImpact
We use one worktree per terminal tab, launched via
claude --worktree <name>. On 2.1.222 that makes the session largely unusable:!bash preamble die at expansion, before running anything. Our session-bootstrap command contains![ "$CLAUDE_AUTONOMOUS" = "1" ] && echo "ON" || echo "off"`` and now fails outright.npx tsx <script>with no inline shell variables.There appears to be no opt-out. No isolation-disable setting exists in the binary —
worktreeIsolation,disableWorktreeIsolation,isolationEnabledall return 0 matches, and the onlyworktree.*keys arelock,baseRef,canonical,sparsePaths,path.ExitWorktreedoes not help either: it only unwinds worktrees created by theEnterWorktreetool, not isolation established by the--worktreelaunch flag.Downgrading does not stick
The obvious mitigation — repoint
~/.local/bin/claudeat 2.1.221 — is silently undone. Launchingclaudere-points the symlink to the newest version present in~/.local/share/claude/versions/:This happens with
"autoUpdates": falseand"installMethod": "native". The new session launched on 2.1.222 and hit the bug again. The only workaround we found was physically moving the 2.1.222 binary out of the versions directory.If auto-updates are disabled, launching should not rewrite the version symlink.
What I'd like to see
echo "$TERM"cannot escape a worktree.--worktree, and it arrived in a patch release with no signal.