Worktree-isolation guard: string-executor check matches every argument position, case-insensitively

Status Open
Reported on v2.1.236
Maintainer reply None cached
Activity 0 comments · opened Aug 28, 2026

Worktree-isolation guard: string-executor check matches every argument, case-insensitively

Version: 2.1.236 (Claude Code)
Platform: Linux (CachyOS, kernel 7.2.0), zsh via the Bash tool
Related: #88776 — same guard, different branch. That issue covers commands
whose AST is not simple. Everything below has a perfectly simple AST.

Summary

While a session is isolated in a worktree, the guard refuses any command in
which any word matches one of a set of string-executing bash builtins. The
check is applied to every argument position, not just command position, and it
is case-insensitive. echo complete is refused.

The tokenizer itself is fine — comments are stripped, quotes resolved, word
boundaries respected. The bug is what the tokens are then compared against, and
where.

Minimal repro

From a session isolated in a worktree (EnterWorktree, or a subagent with
isolation: "worktree"), run via the Bash tool:

echo complete      # REFUSED
echo completely    # runs

Refusal text:

This session is isolated in the worktree \<path\>, but this command runs a string through complete, which can't be verified to stay inside the worktree; run the command directly instead. Refusing to run it — a worktree-isolated session's git operations must target its own worktree.

No git, no path, no pipeline, two tokens.

Full matrix (all as echo <token>, all simple ASTs)

| token | result | note |
|---|---|---|
| eval | refused | bash builtin |
| source | refused | bash builtin |
| trap | refused | bash builtin |
| alias | refused | bash builtin |
| complete | refused | bash builtin |
| bind | refused | bash builtin |
| exec | runs | also invokes; inconsistent with the above |
| command | runs | also invokes; inconsistent |
| builtin | runs | also invokes; inconsistent |
| . | runs | source alias; inconsistent with source |
| bash sh env sudo awk ssh find xargs | run | all can execute strings |

So the list is six bash builtins, and it is neither complete nor consistent —
. is source, and exec/command/builtin invoke too, yet all pass, while
external string-executors (bash -c, awk, ssh, xargs) pass as well.

Two distinct defects

1. Matched in argument position, not just command position.

echo complete        # REFUSED  -- 'complete' is an argument to echo
echo "complete"      # REFUSED  -- quoting does not help
echo 'complete'      # REFUSED  -- single quotes do not help either

Quotes are resolved before the comparison, so there is no way to pass one of
these six words as data.

2. Case-insensitive, although shell builtin names are case-sensitive.

echo COMPLETE        # REFUSED, and the message echoes back "COMPLETE"
echo Alias           # REFUSED, message echoes back "Alias"

COMPLETE and Alias are not bash builtins and could never execute anything.
The refusal message quoting the original casing back is a useful tell that the
comparison is lowercasing before matching.

Correctly handled (for contrast — the tokenizer is not the problem)

echo completely        # runs -- word boundary respected, not substring
echo foo-complete-bar  # runs -- single shell word, correctly not a match
echo trap_door         # runs
echo hi # complete     # runs -- comments stripped before matching

Real-world impact

Any CLI with a subcommand named after one of these six becomes unusable while
isolated. Concretely: a todo-graph tool where query.py complete <id> marks a
node done — a natural subcommand name that collides with a bash builtin. The
workaround was an alternate spelling of the same operation
(query.py update <id> --status done).

Because argument position is matched, this also blocks ordinary text: any
command echoing, grepping for, or passing the literal words complete,
source, alias, trap, bind or eval — all common English — is refused
regardless of context. Verified:

grep -rn "source" README.md    # REFUSED

You cannot grep a repository for the word "source" while isolated. That is a
broad class of false positive across six very common English words.

Suggested fix

  1. Only test the word in command position, not every argument.
  2. Drop the case-insensitivity, or keep it only if the check is also restricted

to command position (where a case-insensitive filesystem could matter).

  1. Decide the list deliberately: either it is "builtins that defer a string"

(then . belongs and bind/alias/trap are arguably about registration,
not immediate execution), or it is "anything that can execute a string"
(then bash -c, awk, xargs, ssh belong). Currently it is neither.

Note that even with argument position fixed, this guard's stated purpose is
keeping git operations inside the worktree; a command with no git in it
arguably should not reach this branch at all — which is the point #88776 makes
about the too-complex bail ordering.

View original on GitHub ↗