[BUG] Bash tool: shell-quote escapes ! to \! in non-interactive shell, breaking jq and other tools
Bug Description
The Bash tool's command quoting pipeline escapes ! to \! before passing commands to a non-interactive shell (bash -c). Since non-interactive shells have history expansion disabled by default, the backslash is treated as a literal character and forwarded to downstream tools like jq, causing syntax errors.
Steps to Reproduce
Run via Claude Code's Bash tool:
cat file.json | jq -r 'keys[] | select(. != "comment")'
Expected
jq receives: keys[] | select(. != "comment")
Actual
jq receives: keys[] | select(. \!= "comment")
jq: error: syntax error, unexpected INVALID_CHARACTER at <top-level>, line 1, column 19:
keys[] | select(. \!= "comment")
^
jq: 2 compile errors
Note: The model generates != correctly — the escaping happens in the tool-to-shell pipeline, not in the model output.
Root Cause
The bundled shell-quote library's quote() function has three quoting paths. When a command string contains both single quotes, double quotes, and whitespace (common in jq filters), it enters the double-quote wrapping path:
// Path 2: string has both ' and " and whitespace → double-quote wrap
if (/["'\s]/.test(s))
return '"' + s.replace(/(["\\$`!])/g, "\\$1") + '"';
// ^ escapes ! to \!
The command then flows through:
1. Agent output: jq -r 'keys[] | select(. != "comment")'
2. $n4() → E7([command, "<", "/dev/null"]) → shell-quote.quote()
3. shell-quote: command has ' " and spaces → path 2 → ! escaped to \!
4. Result: eval "...select(. \!= \"comment\")..." < /dev/null
5. spawn("bash", ["-c", result]) — non-interactive shell, histexpand OFF
6. \! stays literal → jq receives \!= → syntax error
The ! escaping is correct for interactive shells (where ! triggers history expansion), but Claude Code uses spawn() with bash -c which runs a non-interactive shell where ! has no special meaning. The unnecessary \ backslash passes through as a literal character to jq.
Affected Commands
Any command where the full string contains both ' and " (triggering shell-quote's double-quote path) AND uses !:
jqfilters with!=awkexpressions with!~or!=grep -valternatives using!- Any tool receiving
!as part of its syntax
Suggested Fix
Option A: Strip ! from the double-quote escape regex in shell-quote since commands run in non-interactive shells:
// Before
s.replace(/(["\\$`!])/g, "\\$1")
// After
s.replace(/(["\\$`])/g, "\\$1")
Option B: Post-process the quoted command to replace \! back to ! before passing to bash -c.
Option C: Use the single-quote wrapping path (path 1) more aggressively, since single quotes preserve ! literally and non-interactive shells don't need ! escaping.
Workaround
Use | not instead of != in jq:
jq -r '[keys[] | select(. == "comment" | not)]'
Environment
- Claude Code: v2.1.34
- Platform: macOS (Darwin, arm64)
- Shell: zsh/bash
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗