[BUG] Bash tool silently converts ! (0x21) to \! (0x5C 0x21) — corrupts arguments to executables

Resolved 💬 3 comments Opened Feb 27, 2026 by smallscript Closed Mar 27, 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?

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 \!test
  • afm branch new '!man' trunk should 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

  1. Start Claude Code on Windows (claude --chrome or just claude)
  2. Ask Claude to run any command containing !:

``
Run this command for me: printf '%s' '!test'
``

  1. Observe the output is \!test instead 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:

  1. Does not exist on Windows natively — Windows has no bash history
  2. 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
  3. Should not be pre-escaped by the transport layer — even on Linux/macOS, if a user wants ! escaped, bash can handle it via set +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)

View original on GitHub ↗

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