anthropics / anthropics/claude-code
Worktree command-safety analyzer misreads a non-leading `complete`/`compgen`/`compopt` token as an invoked builtin
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
### Environment
- **Claude Code version:** 2.1.207 (native WinGet build, `Anthropic.ClaudeCode`)
- **OS:** Windows 11 (26200), Git Bash + PowerShell tools
- **Component:** the in-worktree Bash command-safety analyzer (a PreToolUse-layer check, *not* the sandbox — `dangerouslyDisableSandbox: true` does not bypass it)
### Summary
In a git-worktree-isolated session, the analyzer that decides whether a Bash command "can be verified to stay inside the worktree" scans the command's tokens for shell-builtin names and treats any occurrence as an *invocation* of that builtin — regardless of the token's position. Because the completion builtins `complete` / `compgen` / `compopt` can execute arbitrary completion functions, a command containing one of those words **as an ordinary argument** is judged unverifiable and refused.
The false positive fires on an ordinary command whose only sin is having the word `complete` somewhere after the executable:
```
py -3 scripts/codex_continuity.py sprint complete --ledger --task-id --next-task-id
```
Here `complete` is a positional argument to `py` (a subcommand name for our own script), not a command being invoked. The analyzer refuses it with:
> This session is isolated in the worktree `…\worktrees\`, 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. …
### Reproduction
1. Create and enter a real git worktree (`git worktree add -b test .git-worktrees/test`).
2. From inside it, run any Bash command that carries `complete`, `compgen`, or `compopt` as a **non-leading** token, e.g.:
- `py -3 anyscript.py sprint complete --flag x`
- `echo complete` *(a bare builtin name as an argument trips it too)*
- `some_tool complete --now`
3. The analyzer refuses the command.
Control cases that are **not** refused, confirming the collision is specifically the builtin-name token and not the surrounding command:
- `py -3 scripts/codex_continuity.py sprint status …`
- `py -3 scripts/codex_continuity.py sprint start …`
- `py -3 scripts/codex_continuity.py sprint approve …`
- `py -3 scripts/codex_continuity.py sprint next …`
Only the subcommand literally named `complete` is blocked.
### Expected vs. actual
- **Expected:** a builtin name counts as "invoking a string-running builtin" only when it is in **command-head position** (the first word of a simple command, i.e. after `;`, `&&`, `||`, `|`, `(`, newline, or the start of the command — and not consumed as an argument to a preceding executable).
- **Actual:** any token equal to a flagged builtin name anywhere in the command is treated as an invocation, so it is refused.
### Impact
This blocks the *documented, gated* completion step of our `/sprint` workflow for **every** worktree-isolated session. Quoting the token (`"complete"`) and `dangerouslyDisableSandbox: true` both fail to bypass it, because it is a PreToolUse-layer analyzer rather than the sandbox. The only bypass we found was an obfuscating Python shim that reassembles the function name at runtime (`getattr(g, 'compl'+'ete')`) — exactly the kind of construct these safety layers should discourage.
More broadly: `complete`, `compgen`, `compopt` are common English/argument words ("mark complete", "complete the run"), so this will surface for any user whose scripts or subcommands use them.
### Suggested fix
When scanning for string-running builtins, gate the match on **command-head position**. A shell-AST/tokenizer pass that already distinguishes a simple command's first word from its arguments should flag the builtin only in that head slot. A token consumed as an argument to a preceding executable (`py`, `echo`, our script, etc.) must not count as an invocation of the builtin of the same name.
The same reasoning applies to the whole builtin set the analyzer keys on — `eval`, `exec`, `source`, `.`, `complete`, `compgen`, `compopt`, etc.: it is the *head-position invocation* that runs a string, not the appearance of the word.
### Workaround in place (our side)
We added a collision-free alias `finish` for the `complete` subcommand of our sprint CLI, routed to the identical handler, and updated the workflow to call `sprint finish` inside worktrees. This is a local mitigation, not a fix for the analyzer — the underlying false positive remains for any command containing these tokens.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start at the in-worktree Bash command-safety analyzer in the PreToolUse layer and reproduce the issue from a real git worktree with the listed commands. Trace how it identifies flagged builtins and compare command-head tokens with arguments. Done means non-leading complete, compgen, and compopt arguments are allowed while actual head-position invocations remain refused.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- bash, powershell, python
- Domaine
- cli, security, tooling
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 48/100