2.1.222: worktree-isolated session refuses any command containing an unresolvable env var (echo "$TERM" is blocked)

Status Open
Reported on v2.1.222
Maintainer reply None cached
Activity 1 comment · opened Aug 5, 2026

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

  1. In a git repo: claude --worktree testwt
  2. 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

  1. Resolve environment variables from the session's real environment before the "simple" classification, instead of refusing on any non-allowlisted variable.
  2. Scope the refusal to commands that actually touch git or filesystem paths. echo "$TERM" cannot escape a worktree.
  3. Provide a documented opt-out setting for the session-level guard.
  4. Fix the message: drop "without the redirect" when there is no redirect, and stop describing non-git commands as git operations.
  5. 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗