[BUG] Bash tool silently converts ! (0x21) to \! (0x5C 0x21) — corrupts arguments to executables
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?
Claude Code's Bash tool silently prepends a backslash (\, 0x5C) to every exclamation mark (!, 0x21) in command strings before they reach the shell or any executable. This happens at Claude Code's transport layer — no shell quoting mechanism prevents it.
Reproduction steps
# Step 1: Verify the bug
printf '%s' '!test'
# Expected output: !test
# Actual output: \!test
# Step 2: Confirm single quotes don't help
printf '%s' '!'
# Expected: !
# Actual: \!
# Step 3: Confirm double quotes don't help
printf '%s' "!"
# Expected: !
# Actual: \!
# Step 4: Confirm the ONLY workaround
printf '%s' $'\x21'
# Expected: !
# Actual: ! ← ANSI-C hex escaping bypasses the transport layer
Impact — real data corruption
This bug caused a Fossil-based SCM repository to be created with branch name \!man instead of !man. The backslash was baked into immutable SHA1-hashed manifest artifacts. The corruption could not be repaired — the entire repository (662 tracked files) had to be destroyed and rebuilt from scratch using the $'\x21' workaround.
Workaround
Replace every ! with ANSI-C hex escape $'\x21':
# Instead of:
afm branch new '!man' trunk # BROKEN — creates \!man
# Use:
afm branch new $'\x21man' trunk # Works — creates !man
This works because $'\x21' is processed by bash after Claude Code's transport layer has finished mangling.
What Should Happen?
The literal character ! (0x21) should be passed to bash unchanged, just like every other printable ASCII character. Bash's own history expansion (set +H / set -H) should be the user's concern, not Claude Code's.
echo '!test'should output!test, not\!testafm branch new '!man' trunkshould create branch!man, not\!man- SQL queries containing
!=should work, not become\!=
The fix should be straightforward: stop pre-processing ! in command strings before passing them to the shell. If the intent was to escape bash history expansion, that's the wrong approach — it should be handled by bash itself (set +H), not by the transport layer, because the escaping also affects non-bash contexts (arguments to executables, SQL strings inside arguments, etc.).
Error Messages/Logs
$ printf '%s' '!test'
\!test
$ printf '%s' '!'
\!
$ printf '%s\n' "!"
\!
$ printf '%s' $'\x21'
!
Steps to Reproduce
- Start Claude Code on Windows (
claude --chromeor justclaude) - Ask Claude to run any command containing
!:
````
Run this command for me: printf '%s' '!test'
- Observe the output is
\!testinstead of!test
Minimal reproduction (no project context needed):
printf '%s' '!'
Expected: !
Actual: \!
The ! character is commonly used in:
- Branch names and file paths (our project has
!man/,!aftd/,!open-projects/) - SQL
!=operator (must use<>instead) - Bash history expansion (which users may legitimately want)
Note: This is on Windows 10, where bash history expansion (! as a special character) is not a native concern — Windows has no bash history. Claude Code provides its own bash shell on Windows, making the ! escaping even more surprising since the user never opted into bash history expansion semantics.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
1.1.4088.0 (Claude Desktop, MSIX: Claude_1.1.4088.0_x64__pzs8sxrjxfjjc)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
Windows-specific: bash has no place here
On Windows, Claude Code provides its own bash shell — the user's native environment is cmd.exe/PowerShell, not bash. Bash history expansion (the ! special character that this escaping appears to target) is a bash-interactive-mode concern that:
- Does not exist on Windows natively — Windows has no bash history
- Is not active in non-interactive bash — Claude Code's Bash tool runs commands non-interactively, so
set -H(history expansion) is OFF by default - Should not be pre-escaped by the transport layer — even on Linux/macOS, if a user wants
!escaped, bash can handle it viaset +H/set -H
The transport-layer escaping is wrong on every platform, but it's especially wrong on Windows where the user never asked for bash, never opted into bash history semantics, and has no way to control or disable the escaping.
Related: $_ is also mangled by the Bash tool transport layer
The same transport layer that escapes ! also interferes with $_ — a bash special variable (last argument of previous command). When piping PowerShell commands through the Bash tool, any $_ in ForEach-Object pipelines gets eaten by bash string interpolation BEFORE PowerShell sees it. The workaround is to write .ps1 files and invoke with powershell -File script.ps1 instead of inline -Command strings. This is the same class of bug: the transport layer pre-processes shell metacharacters that should be passed through verbatim.
Environment
- OS: Windows 10 Pro 10.0.19045
- Shell: bash (via Claude Code's Bash tool — not user-installed)
- Claude Desktop: MSIX-packaged (AppX),
C:\Program Files\WindowsApps\Claude_1.1.4088.0_x64__pzs8sxrjxfjjc - Model: claude-opus-4-6
- Platform: Anthropic API (Max plan)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗