Preflight Checklist
What's Wrong?
Bug report and tests written by Claude Code, but double-checked by me.
Summary
The Claude Code Bash tool silently expands all environment variables to empty strings when the command is a simple pipeline and does not have a # comment on a preceding line. This produces incorrect results with no warning, and the workaround (adding a comment) is non-obvious.
Impact
This is a silent correctness bug — commands produce wrong results with no error or warning. It is particularly insidious because:
- It's intermittent in practice. Developers (and AI agents) habitually add
# descriptive comment lines before commands. When they do, the bug doesn't trigger. When they don't, it does. This makes it hard to diagnose.
- The failure mode is misleading. When a variable is used in a file path, the error looks like a path problem (
bash: /scripts/setup.sh: No such file or directory), not an env var expansion issue. Agents waste time investigating the wrong cause.
- It was discovered by accident. A Claude Code subagent failed to run a script, retried 3 times, then worked around it by hardcoding the path — all because it happened not to include a
# comment in its first few attempts.
Analysis
-
The variables exist in the process environment. env | grep VAR always shows the correct value. printenv VAR | cat also works. The variables are exported and present.
-
Shell $-expansion is broken, not the environment. The issue is specifically that $VAR syntax expands to an empty string. Commands that read from the environment directly (env, printenv) are unaffected.
-
The issue only manifests in pipelines. The same $VAR expansion works correctly when there is no | in the command.
-
A # comment on a preceding line is a reliable workaround. This suggests the Bash tool has two code paths for executing commands, selected based on whether a # character appears in the input. The non-# path has a bug where environment variables are not available during shell expansion of pipelines.
-
Compound commands (for, if/then) also work. These change how the shell parses the command, which may route through a different execution path.
-
The set -x trace confirms eval is used. Trace output shows (eval):1> prefixes. The bug likely lies in how the command string is passed to eval or in environment setup before eval runs, conditional on the presence of #.
What Should Happen?
Environment variables should expand identically regardless of whether the command contains a pipe, and regardless of whether a # comment precedes the command.
Error Messages/Logs
Steps to Reproduce
Just ask Claude Code to run the following bash commands.
Failing cases — env vars silently expand to empty
Test 1: Simple pipe — $HOME expands to empty, so echo prints a blank line, and cat outputs nothing.
Output: (empty)
Test 2: Quoting doesn't help.
Output: (empty)
Test 3: Brace syntax doesn't help.
Output: (empty)
Test 4: It's not just echo and cat. When the variable is in a path argument, the empty expansion produces a bogus path.
bash "$HOME/some/script.sh" | head -1
Output (stderr): bash: /some/script.sh: No such file or directory
Test 5: All variables are affected, not just $HOME.
echo "HOME=$HOME USER=$USER" | cat
Output: HOME= USER=
Test 6: Locally assigned variables are also affected.
Output: (empty)
Test 7: Same with && instead of ;.
Output: (empty)
Test 8: Same with a newline separator.
Output: (empty)
Test 9: A preceding no-op command doesn't help.
Output: (empty)
Test 10: A preceding true doesn't help.
Output: (empty)
Test 11: A preceding echo doesn't help.
echo hi && echo $HOME | cat
Output: hi
Test 12: A blank preceding line doesn't help.
Output: (empty)
Test 13: A trailing comment on the same line doesn't help.
echo $HOME | cat # this is a comment
Output: (empty)
Passing cases
Test 14: Adding a comment line before the pipeline makes it work.
# comment
echo $HOME | cat
Output: /home/user
Test 15: Even an empty comment works.
Output: /home/user
Test 16: Wrapping in a for loop works too.
for i in 1; do echo $HOME | cat; done
Output: /home/user
Test 17: Wrapping in if/then.
if true; then echo $HOME | cat; fi
Output: /home/user
Test 18: Using command substitution with printenv (bypasses shell $-expansion entirely).
echo $(printenv HOME) | cat
Output: /home/user
Test 19: Without a pipe, variable expansion works fine.
Output: /home/user
Test 20: Redirects work fine too.
Output: /home/user
Test 21: env and printenv in a pipe work because they read from the process environment directly, without shell $-expansion.
Output: HOME=/home/user
Output: /home/user
Diagnostic — set -x trace
Test 22: With set -x enabled, the shell prints each command after expansion. The trace reveals that the Bash tool executes commands via eval, and that $HOME has expanded to an empty string before execution.
set -x; echo "$HOME" | cat; set +x
Output:
+(eval):1> echo ''
+(eval):1> cat
+(eval):1> set +x
The (eval):1> prefix confirms the command runs inside eval. The trace shows echo '' — the variable expanded to an empty string.
Test 23: For comparison, the same command with a # comment prefix shows correct expansion.
# comment
set -x; echo "$HOME" | cat; set +x
Output:
+(eval):2> echo /home/user
+(eval):2> cat
/home/user
+(eval):2> set +x
Note (eval):2> instead of (eval):1> — the comment occupies line 1, pushing the command to line 2. The trace shows echo /home/user — the variable expanded correctly.
Claude Model
Not sure / Multiple models
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Best guess: 2.1.50 (but probably later versions still worked too)
Claude Code Version
2.1.62
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
iTerm2
Additional Information
No response
Preflight Checklist
What's Wrong?
Bug report and tests written by Claude Code, but double-checked by me.
Summary
The Claude Code Bash tool silently expands all environment variables to empty strings when the command is a simple pipeline and does not have a
#comment on a preceding line. This produces incorrect results with no warning, and the workaround (adding a comment) is non-obvious.Impact
This is a silent correctness bug — commands produce wrong results with no error or warning. It is particularly insidious because:
# descriptive commentlines before commands. When they do, the bug doesn't trigger. When they don't, it does. This makes it hard to diagnose.bash: /scripts/setup.sh: No such file or directory), not an env var expansion issue. Agents waste time investigating the wrong cause.# commentin its first few attempts.Analysis
The variables exist in the process environment.
env | grep VARalways shows the correct value.printenv VAR | catalso works. The variables are exported and present.Shell
$-expansion is broken, not the environment. The issue is specifically that$VARsyntax expands to an empty string. Commands that read from the environment directly (env,printenv) are unaffected.The issue only manifests in pipelines. The same
$VARexpansion works correctly when there is no|in the command.A
#comment on a preceding line is a reliable workaround. This suggests the Bash tool has two code paths for executing commands, selected based on whether a#character appears in the input. The non-#path has a bug where environment variables are not available during shell expansion of pipelines.Compound commands (
for,if/then) also work. These change how the shell parses the command, which may route through a different execution path.The
set -xtrace confirmsevalis used. Trace output shows(eval):1>prefixes. The bug likely lies in how the command string is passed toevalor in environment setup beforeevalruns, conditional on the presence of#.What Should Happen?
Environment variables should expand identically regardless of whether the command contains a pipe, and regardless of whether a
#comment precedes the command.Error Messages/Logs
Steps to Reproduce
Just ask Claude Code to run the following bash commands.
Failing cases — env vars silently expand to empty
Test 1: Simple pipe —
$HOMEexpands to empty, soechoprints a blank line, andcatoutputs nothing.Output: (empty)
Test 2: Quoting doesn't help.
Output: (empty)
Test 3: Brace syntax doesn't help.
Output: (empty)
Test 4: It's not just
echoandcat. When the variable is in a path argument, the empty expansion produces a bogus path.Output (stderr):
bash: /some/script.sh: No such file or directoryTest 5: All variables are affected, not just
$HOME.Output:
HOME= USER=Test 6: Locally assigned variables are also affected.
Output: (empty)
Test 7: Same with
&&instead of;.Output: (empty)
Test 8: Same with a newline separator.
Output: (empty)
Test 9: A preceding no-op command doesn't help.
Output: (empty)
Test 10: A preceding
truedoesn't help.Output: (empty)
Test 11: A preceding
echodoesn't help.Output:
hiTest 12: A blank preceding line doesn't help.
Output: (empty)
Test 13: A trailing comment on the same line doesn't help.
Output: (empty)
Passing cases
Test 14: Adding a comment line before the pipeline makes it work.
Output:
/home/userTest 15: Even an empty comment works.
Output:
/home/userTest 16: Wrapping in a
forloop works too.Output:
/home/userTest 17: Wrapping in
if/then.Output:
/home/userTest 18: Using command substitution with
printenv(bypasses shell$-expansion entirely).Output:
/home/userTest 19: Without a pipe, variable expansion works fine.
Output:
/home/userTest 20: Redirects work fine too.
Output:
/home/userTest 21:
envandprintenvin a pipe work because they read from the process environment directly, without shell$-expansion.env | grep HOMEOutput:
HOME=/home/userprintenv HOME | catOutput:
/home/userDiagnostic —
set -xtraceTest 22: With
set -xenabled, the shell prints each command after expansion. The trace reveals that the Bash tool executes commands viaeval, and that$HOMEhas expanded to an empty string before execution.Output:
The
(eval):1>prefix confirms the command runs insideeval. The trace showsecho ''— the variable expanded to an empty string.Test 23: For comparison, the same command with a
#comment prefix shows correct expansion.Output:
Note
(eval):2>instead of(eval):1>— the comment occupies line 1, pushing the command to line 2. The trace showsecho /home/user— the variable expanded correctly.Claude Model
Not sure / Multiple models
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Best guess: 2.1.50 (but probably later versions still worked too)
Claude Code Version
2.1.62
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
iTerm2
Additional Information
No response