Bash tool silently strips -flag tokens when a command has a command substitution with a nested $

Resolved 💬 1 comment Opened Jun 1, 2026 by neon-horizon Closed Jul 4, 2026

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 -> cut1
  • v=$(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 -> cut1
  • cut -c1-12 -> cut1-12
  • grep -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.

View original on GitHub ↗

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