Bash tool silently strips -flag tokens when a command has a command substitution with a nested $
Bash tool strips -flag tokens when a command contains a command
substitution with a nested $-expansion
Summary
When a Bash tool command contains a command substitution — $(…) or
backticks — that itself contains a $-parameter expansion (e.g.$(echo $HOME), $(git rev-parse $branch)), the harness strips every -<flag> token from the command before it is executed. The model sends a
correct command; a corrupted command is what actually runs in the shell.
Environment
- Claude Code: 2.1.159
- OS: macOS (Darwin 24.6.0), Apple Silicon
- Shell: /bin/zsh 5.9 (commands run via eval; ZSH_EVAL_CONTEXT=cmdarg:eval)
Minimal reproducer (100% deterministic)
Run this as a single Bash tool call:
v=$(echo $HOME)
printf 'a\tb\n' | cut -f1
Expected: prints a
Actual: (eval):2: command not found: cut1
(the -f is stripped; cut -f1 becomes cut1)
Trigger isolation (bisected)
FAILS (flag tokens stripped from the whole command):
v=$(echo $HOME); printf 'a\tb\n' | cut -f1->cut1v=$(echo $$)+cut -f1->cut1- `
v=echo $HOME; …| cut -f1->cut1` (backticks too)
WORKS:
v=$(echo hi)+cut -f1(substitution, no inner$)v=$(ls -d /tmp)+cut -f1(flag inside sub, no inner$)v=$HOME+cut -f1(param expansion, no substitution)- bare
echo $$+cut -f1 - standalone
cut -f1
So the precise trigger is: a command substitution containing a $-expansion,
plus any -flag token elsewhere in the same command.
Corruption signature
Every -<flag> run (short or long) is deleted; glued values survive:
cut -f1->cut1cut -c1-12->cut1-12grep -iE -A1->grep1--format=%h->=%h(e.g.git log -1 --format=%h->git log -1=%h
-> fatal: '1=%h': not an integer)
python3 -c "…"->python3 "…"(code string treated as a filename)ps -p $$ -o pid=->ps pid=
Proof this is harness-side, not the model
Parsing the session transcripts (~/.claude/projects/**/*.jsonl) shows the
recorded tool_input.command is correct (… | cut -c1-12) while the tool
result errors with cut1-12. The corruption occurs between the recorded tool
input and shell evaluation.
Ruled out
- User shell config: snapshot has no global aliases (
alias -g), no relevant
setopt, no command_not_found_handler.
- Not a byte-offset/transport drop: prepending padding of varying length does
not move which token is corrupted.
- Not persistent-shell state corruption: a standalone command runs correctly
immediately after a failing one.
- Newlines vs
;is irrelevant; multi-line is not required.
Impact
$(… $var …) is extremely common in real shell usage, so a large fraction of
non-trivial commands silently execute with their flags stripped — frequently
producing wrong results rather than clean errors.
Workarounds (current)
- Split the substitution and the flag-bearing command into separate Bash calls
(capture the substitution's output, inline the literal; env vars do not
persist across Bash calls).
- Run a script file instead of an inline nested one-liner.
Note: quoting the inner var, ; vs newline, and backticks vs $() do NOT help.
Likely area
Command-preprocessing/parsing layer (permission-prefix extraction or command
serialization) mis-handling nested $ inside command substitutions and
dropping option tokens as a side effect.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗