[BUG] Complex bash syntax fails with preprocessing (reproducible, workaround exists but inconsistent)

Resolved 💬 14 comments Opened Nov 7, 2025 by binaryphile Closed Mar 7, 2026

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?

Complex bash patterns involving pipes fail with silent data loss or syntax errors. Loop variables are silently stripped when piped, causing commands to produce wrong output with no error message. Command substitution with pipes causes syntax errors. JavaScript template literals in heredocs are rejected as "Bad substitution" errors.

This appears to be a regression of the v1.0.77 fix for "heredoc and multiline string escaping." The workaround (bash -c) is documented in Issue #774 but practically unusable - even Claude Code itself can't consistently apply it when generating commands.

Related to issues #9323, #8318, and #11182, which appear to be different manifestations of the same preprocessing issue.

What Should Happen?

Valid bash syntax should execute correctly:

  • Loop variables should retain their values when piped
  • Command substitution with pipes should work
  • Heredocs with non-bash syntax (like JavaScript) should be treated as literal text
  • Multi-line loops should preserve newlines

These patterns work in standard terminals and when wrapped in bash -c, indicating they're valid bash syntax being incorrectly rejected or transformed by preprocessing.

Error Messages/Logs

# Symptom 1: Silent data loss (CRITICAL)
$ for i in one two three; do echo "Item: $i" | cat; done
Item:
Item:
Item:

# Variables completely stripped, no error message

# Symptom 2: Command substitution syntax error
$ result=$(echo "test" | tr a-z A-Z); echo "Result: $result"
bash: -c: line 1: syntax error near unexpected token `|'

# Symptom 3: JavaScript template literal rejection
$ cat <<'EOF'
return `<div>${escapeHtml(cmd)}</div>`
EOF
Failed to parse command: Bad substitution: escapeHtml

# Symptom 4: Variable cleared by pipe
$ export TEST_VAR=alpha && echo "$TEST_VAR" | hexdump -C
00000000  0a                                                |.|
00000001
# Only shows newline (0a), "alpha" completely gone

Steps to Reproduce

Primary reproduction (CRITICAL silent data loss):

  1. Run this command:
for i in one two three; do echo "Item: $i" | cat; done
  1. Expected output:
Item: one
Item: two
Item: three
  1. Actual output:
Item:
Item:
Item:
  1. Verification: Run same command 3 times - get identical wrong output (100% reproducible)
  1. Workaround that works:
bash -c 'for i in one two three; do echo "Item: $i" | cat; done'

Produces correct output with variables intact.

Additional reproductions:

Command substitution with pipes:

result=$(echo "test" | tr a-z A-Z); echo "Result: $result"

Error: bash: -c: line 1: syntax error near unexpected token '|'

JavaScript in heredoc:

cat <<'EOF'
return `<div>${escapeHtml(cmd)}</div>`
EOF

Error: Failed to parse command: Bad substitution: escapeHtml

Variable cleared by pipe:

export TEST_VAR=alpha && echo "$TEST_VAR" | wc -c

Expected: 6, Actual: 1 (verified with hexdump - only newline remains)

All patterns fixed by bash -c workaround.

Claude Model

Sonnet (default)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

v1.0.77 (CHANGELOG claimed "Fixed heredoc and multiline string escaping" but issue persists or regressed in v2.0.28. Issue #4315 filed July 2025 after claimed fix.)

Claude Code Version

v2.0.28

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Note: this was written with assistance from Claude Code. The "we" refers to the two of us.

Related Issues (Reproduced Identically)

  • #9323 - JavaScript template literals in heredocs rejected
  • #8318 - Environment variables cleared when piped
  • #11182 - For loop newlines stripped causing syntax errors

Note on scope: While we document multiple symptoms, this is a single bug about preprocessing being too aggressive on heredoc/multiline/escaping. All symptoms share:

  • Common root cause (preprocessing transformations)
  • Same workaround (bash -c)
  • Same pattern (pipes and complex substitutions affected)
  • Deterministic behavior

Severity Assessment

CRITICAL (3 symptoms):

  1. Silent data loss - Loop variables stripped with no error (Symptom 1)
  2. Command substitution broken - $(cmd | cmd) completely non-functional (Symptom 2)
  3. Variable clearing - Export vars disappear in pipes with no error (Symptom 4)

HIGH (2 symptoms):

  • For loop newlines stripped → syntax errors
  • Command groups { ... } treated as literal commands

MEDIUM (1 symptom):

  • False positive security rejection of valid JavaScript in config files

Observations

Behavior suggests sophisticated processing:

  • Context-aware: cat <<'EOF'\nrm -rf /\nEOF works correctly - understands dangerous commands are safe in quoted heredocs
  • Parses syntax: Error "Bad substitution: escapeHtml" extracts function name from ${escapeHtml(...)}
  • Deterministic: Variation tests (same command 3x) produced identical results every time
  • Pre-bash transformation: Error messages reference mangled syntax (escaped \$), indicating transformation before bash sees command

Consistent failure patterns:

  • All failures involve pipes OR complex substitutions
  • Simple commands without pipes work
  • All fixed by bash -c wrapper (also sh -c, dash -c, eval)
  • Error format: "Failed to parse command: Bad substitution: FUNCTION_NAME"

What Works (Scope Narrowing)

  • Simple heredocs without complex substitutions
  • Basic pipes without loops or command substitution
  • C-style for loops: for ((i=0; i<3; i++)); do echo $i; done
  • Commands without pipes generally work

Workarounds

Primary workaround: bash -c 'script' (from Issue #774)

  • Tested against all failing patterns: 100% success rate
  • Also works: sh -c, dash -c, eval, pipes to bash

Why unusable in practice:

During this investigation, Claude Code itself repeatedly failed to apply its own workaround when generating commands - even while being keenly aware of it due to being in the process of documenting the very issue. This creates a cycle:

  1. Ask Claude Code to run complex bash command
  2. It generates direct bash (forgetting workaround)
  3. Command fails with preprocessing error
  4. Must remind Claude Code to use bash -c
  5. Repeat for every complex command

If Claude Code can't reliably apply this, human users face even more difficulty:

  • Remembering which patterns need it (not obvious from syntax)
  • Complex quoting transformations: <<'EOF'bash -c 'cat <<'\''EOF'\'''
  • Constant vigilance

Discoverability: Workaround only found through Issue #774 - not obvious to users encountering errors.

Impact

User impact:

  • At least 3 other users reported identical symptoms (issues #9323, #8318, #11182)
  • Silent data loss bugs (no error, wrong output) can be dangerous
  • Command substitution with pipes completely non-functional
  • Workaround exists but practically inconsistent

Organizational adoption impact:

I'm the engineer championing Claude Code adoption in our mid-sized organization. If I can't get this addressed, it will seriously impair my ability to advocate for broader adoption.

This isn't pressure - I want to recommend Claude Code. But when basic bash patterns fail unpredictably, it undermines confidence. Other engineers ask "why does this loop work here but fail there?" and I don't have good answers.

External Documentation

According to publicly documented architecture analysis (Shatrov, K. 2025), the Bash tool performs preprocessing with up to two passes before execution. Our observations align with this documented behavior.

Reference: Shatrov, K. (2025). Reverse engineering Claude Code. https://kirshatrov.com/posts/claude-code-internals

Possible Approaches (Suggestions, Not Prescriptions)

We don't know your constraints or security requirements, but wanted to offer possibilities:

  1. Refine detection rules - Reduce false positives while maintaining security
  • Pro: Better precision, fewer legitimate commands blocked
  • Con: Complex to implement, risk of bypasses
  1. User opt-out flag - Like dangerouslyDisableSandbox parameter
  • Pro: Explicit user choice, maintains security by default
  • Con: Support burden, user education, potential misuse
  1. Better error messages - Explain preprocessing and suggest bash -c workaround
  • Pro: Improves discoverability, lower implementation risk
  • Con: Doesn't fix underlying issue, workaround still hard to use
  1. Graduated strictness - Simple commands lenient, complex strict
  • Pro: Balance safety and usability
  • Con: Complex implementation, defining "simple" vs "complex" challenging
  1. Teach Claude Code the workaround - Update system prompt for bash -c usage
  • Pro: Would make workaround more usable
  • Con: Still a workaround, doesn't fix root cause

We aren't the judge of what's feasible given your security requirements.

Acknowledgments

Positive observations:

  • Team engaged constructively on Issue #774 (@dicksontsai)
  • Appreciate security focus (CVE-2025-54795 fix demonstrates commitment)
  • Understand this is difficult security vs usability tradeoff
  • Issue #4315 (heredoc mangling) open since July 2025 shows active work in this area

Complexity recognition:

We recognize that balancing security (preventing command injection) with usability (supporting legitimate bash patterns) is extremely challenging. We're reporting these edge cases as data points to help improve the system, not as criticism.

The fact that preprocessing is context-aware (understands quoted heredocs, parses syntax) shows sophisticated engineering. These issues appear to be overly aggressive rules rather than fundamental flaws.

Offer to Help

Happy to:

  • Test any proposed fixes or experimental builds
  • Provide additional reproduction cases
  • Clarify any findings
  • Try different bash versions/environments for debugging
  • Provide more detailed traces/logs if diagnostic information available

Thank you for your work on Claude Code and for considering this report.

View original on GitHub ↗

This issue has 14 comments on GitHub. Read the full discussion on GitHub ↗