[BUG] Bash tool: shell-quote escapes ! to \! in non-interactive shell, breaking jq and other tools

Resolved 💬 3 comments Opened Feb 6, 2026 by move-hoon Closed Feb 10, 2026

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 !:

  • jq filters with !=
  • awk expressions with !~ or !=
  • grep -v alternatives 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

View original on GitHub ↗

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