[BUG] Bash Tool Silently Empties Environment Variables in Pipelines
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
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 commentlines 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
# 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 — $HOME expands to empty, so echo prints a blank line, and cat outputs nothing.
echo $HOME | cat
Output: (empty)
---
Test 2: Quoting doesn't help.
echo "$HOME" | cat
Output: (empty)
---
Test 3: Brace syntax doesn't help.
echo ${HOME} | cat
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.
X=hello; echo $X | cat
Output: (empty)
---
Test 7: Same with && instead of ;.
X=hello && echo $X | cat
Output: (empty)
---
Test 8: Same with a newline separator.
X=hello
echo $X | cat
Output: (empty)
---
Test 9: A preceding no-op command doesn't help.
: && echo $HOME | cat
Output: (empty)
---
Test 10: A preceding true doesn't help.
true && echo $HOME | cat
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.
echo $HOME | cat
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.
#
echo $HOME | cat
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.
echo $HOME
Output: /home/user
---
Test 20: Redirects work fine too.
echo $HOME 2>&1
Output: /home/user
---
Test 21: env and printenv in a pipe work because they read from the process environment directly, without shell $-expansion.
env | grep HOME
Output: HOME=/home/user
printenv HOME | cat
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_
This issue has 10 comments on GitHub. Read the full discussion on GitHub ↗