2.1.222: worktree-isolated session refuses any command containing an unresolvable env var (echo "$TERM" is blocked)
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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗