[BUG] Bash tool escapes ! inside single-quoted strings, breaking jq != and other commands
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?
The Bash tool escapes ! to \! even inside single-quoted strings, where bash treats all characters literally. This breaks any command containing ! in single-quoted arguments, most commonly jq's != operator.
Example: Claude generates echo 'hello != world' — the Bash tool transforms it to echo 'hello \!= world' — output is hello \!= world instead of hello != world.
This is particularly disruptive with gh + --jq expressions that use != for filtering, which is a very common pattern when working with GitHub APIs.
What Should Happen?
The Bash tool should pass single-quoted strings through without modification. Inside single quotes, ! has no special meaning in bash and must not be escaped.
Error Messages/Logs
# Bash tool escapes ! inside single quotes:
$ echo 'hello != world'
hello \!= world
# jq expressions with != fail:
$ gh api ... --jq '.[] | select(.name != "bob")'
failed to parse jq expression: unexpected token "\\"
# Verification that bash itself is NOT the cause — writing to a script file
# bypasses the Bash tool's escaping:
$ cat <<'SCRIPT' > /tmp/test.sh
echo 'hello != world'
SCRIPT
$ bash /tmp/test.sh
hello != world
Steps to Reproduce
- Start a Claude Code session
- Ask Claude to run a command containing
!=in single quotes, e.g.:echo 'hello != world' - Observe the output:
hello \!= world(escaped) - For comparison, write the same command to a script file and execute it via
bash /tmp/script.sh— output is correct:hello != world
This confirms the escaping happens in the Bash tool layer, not in bash itself.
Claude Model
Opus
Is this a regression?
I don't know
Claude Code Version
2.1.52 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Non-interactive/CI environment
Additional Information
Workarounds:
- Use
| notinstead of!=in jq (e.g.,select(.name == "bob" | not)) - Write the command to a temporary script file and execute via
bash /tmp/script.sh
Both are viable but add friction and waste context when Claude retries after the initial failure.
✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗