Bash tool silently collapses `\\` to `\` in command text, corrupting regex and paths
Summary
The Bash tool collapses the two-character sequence \\ into a single \ in the
command string before the shell parses it. This happens inside single quotes,
inside double quotes, and inside a quoted heredoc (<<'EOF') alike, so the POSIX
guarantee that single quotes preserve backslashes literally does not hold.
It is not a general escape-decode — \n passes through untouched. It is specifically
the two-backslash sequence.
The failure is silent: a mangled backslash escape usually remains valid regex,
so sed/grep/awk match something different, exit 0, and report success.
Environment
| | |
| --- | --- |
| Claude Code | 2.1.224 |
| OS | Windows 11 Pro 10.0.26200 |
| Shell | Git Bash — GNU bash 5.3.9(1)-release (x86_64-pc-cygwin), MINGW64_NT-10.0-26200 |
| Tools affected | Bash tool only. The PowerShell tool is not affected. |
Only tested on Windows/Git Bash. Whether macOS and Linux shells are affected is
untested and worth checking.
Minimal reproduction
printf '%s\n' 'a\\b'
Expected: a\\b (single quotes are literal in POSIX sh)
Actual: a\b
Observed mapping, across every quoting form including quoted heredocs:
| Typed in a Bash command | What the shell receives |
| --- | --- |
| \ | \ |
| \\ | \ ← one is eaten |
| \\\\ | \\ |
Proof that it is the tool layer, not bash or MSYS2
Same bytes, same bash, same quoting — only the transport differs.
Write a script file containing exactly (verified on disk with od -c):
printf 'from a SCRIPT FILE: [%s]\n' 'a\\b'
Then compare:
$ bash /path/to/repro.sh
from a SCRIPT FILE: [a\\b] <-- correct
$ (same line typed into the Bash tool)
TYPED INTO TOOL: [a\b] <-- one backslash eaten
Because bash reading the identical text from disk behaves correctly, this isolates
the defect to the Bash tool's command transport. It is not bash, and not MSYS2/Cygwin
argv path conversion (which would not affect heredoc content anyway — heredoc bodies
are script text, not argv, and they are collapsed too).
Why this is worse than a normal escaping bug
It corrupts data while reporting success. Given a file containing a\b:
sed 's/a\\b/[HIT]/' file
The author intends a literal backslash. sed instead receives a\b, where \b is
GNU sed's word-boundary metacharacter — a perfectly valid pattern. sed matches
just the a, substitutes, exits 0, and writes the file.
Verified byte-for-byte, the result is [HIT]\b, not [HIT].
Nothing errors at any layer. In our case this presented as sed -i and perl -i
calls that exited 0 with the file's md5 unchanged — which reads exactly like a
blocked or sandboxed write, sending us down a permissions/filesystem dead end. The
writes were fine; the patterns simply never matched.
awk is the only tool that hints at it, warning:
awk: warning: escape sequence `\S' treated as plain `S'
Impact
Any Bash-tool command that needs a literal backslash is affected, most commonly:
sed/grep/awk/perlpatterns matching literal backslashes- Windows path manipulation where paths are escaped as
\\ - regex escapes generally, where the eaten backslash silently changes the pattern's
meaning rather than producing an error
Agents that store shell commands in instruction/config files and later type them into
the Bash tool inherit the bug at execution time, even though the stored text is correct.
Workarounds
- Double every literal backslash: type
\\\\to get\\. - Use the Edit/Write tools or the PowerShell tool for anything involving backslashes.
- Put the command in a script file and execute that — script files are immune.
- Build the backslash from octal inside the shell, e.g.
PAT=$(printf '\134\134'),
which sidesteps the transport entirely.
Suggested fix
Stop unescaping \\ in the command string, or document the transport's escaping
contract explicitly. Failing that, surfacing a warning when a command contains \\
would at least make the corruption visible rather than silent.
3 Comments
Confirming on a second shell: this reproduces identically under MSYS2 bash, so it is not specific to Git for Windows. Claude Code 2.1.231, Windows 11 — still present two versions on from the report.
Swapping
CLAUDE_CODE_GIT_BASH_PATHbetween the two shells and running the same probe through the Bash tool gives byte-identical results:| shell |
uname| bytes received || --- | --- | --- |
|
C:\msys64\usr\bin\bash.exe|MSYS_NT-10.0-26200|a \ \ b(4 chars) ||
C:\Program Files\Git\bin\bash.exe|MINGW64_NT-10.0-26200|a \ \ b(4 chars) |Since single quotes cannot remove a backslash, the collapse has already happened before bash parses anything — consistent with the transport diagnosis above.
Three further data points:
Only the pair collapses.
\$,\nand\qall arrive intact —\nstays as backslash-n rather than becoming a newline — which rules out a JSON or C-style unescape as the mechanism.MCP tool arguments are unaffected. Passing
pair:a\\b single:a\nb dollar:\$Xas an argument to an MCP tool and reading what the server received gives all four backslashes intact. So it is the Bash tool's command composition specifically, not tool argument transport generally.The wrapper is visible from inside.
BASH_EXECUTION_STRINGshows the command is spliced into a larger script before execution:so the escaping happens while that string is composed, which matches the observed behaviour being identical across shell binaries.
On the silent-corruption point in the report: a count assertion in front of any replacement turns it from silence into a stop. Every occurrence encountered here failed loudly for that reason alone —
assert s.count(old) == 1before writing.Data point: the Linux answer to "whether macOS and Linux shells are affected is untested"
Not reproducible on Linux at 2.1.241. Ran your exact probe plus an
od -cvariant through the Bash tool on Ubuntu (GNU bash, x86_64-pc-linux-gnu):Four characters, both backslashes intact. Your table's collapsing row does not occur here, so on the evidence so far this is a Windows-side transport defect rather than a universal one. macOS is still untested by me.
Independent confirmation of the quoted-heredoc case, and the same negative control for it
A separate reporter on Windows 11 / Git Bash hit this while writing throwaway analysis scripts, and the heredoc body is where it cost the most, because the resulting error accuses the generated file's content at a line nobody wrote:
/[.*+?^${}()|[\]\\]/glanded on disk as/[.*+?^${}()|[\]\]/g— the class then swallows the/and Node reportsSyntaxError: Invalid regular expression: missing /;.replace('\\','/')landed as.replace('\','/')—SyntaxError: unterminated string literal.Both misdirect toward rewriting the regex rather than suspecting the write channel, which matches the
sed-word-boundary shape in your report: the corrupted form stays plausible, so nothing errors at the layer that did the damage.The same body through the same tool on the Linux box above round-trips byte-exact:
On the workaround list
Worth flagging the cost of workaround 1 (double every literal backslash) for anyone writing it into standing instructions: it is correct only while the transport is broken, and inverts into a doubled-backslash bug the day this is fixed — on a channel that surfaces no version signal either way. Workarounds 2 and 3 (
Writetool; put it in a script file and execute that) don't have that property, and we have standardised on those.Still reproduces at 2.1.250, Windows 11 Home 10.0.26200, Git Bash — five versions on from the report.
The PowerShell tool, same machine and same literal, is unaffected (
len=5, bytes65,92,92,66... i.e. both backslashes intact), which matches your isolation of the transport layer.A failure mode not yet in this thread: string literals in interpreted languages
The report covers regex and paths. It also corrupts string literals, and there the mangled result is not merely a different-but-valid pattern — it is a control character.
A heredoc'd Python line:
arrives at Python as
"\begin{thebibliography}", which Python then parses as an escape sequence. The variable ends up holding a literal 0x08 backspace:So a substring test against a LaTeX file returned
Falseinstead of erroring, and an edit script reported "anchor not found" against a file that plainly contained the anchor. Silent, and it looks like a logic bug in the script rather than a transport defect — I spent two failed tool calls and a wrong diagnosis ("quoted heredocs strip backslashes") before the\\-specific mapping in this issue explained it.Worth noting for anyone landing here from a mangled-escape symptom: the collapsing is invisible in the tool's own echo of the command, so the first suspicion always falls on the script.
Workaround
Beyond writing the file to disk and executing it, raw strings survive intact, because single backslashes pass through untouched:
That avoids the write-to-disk detour for the common case of embedding LaTeX, Windows paths, or regex in a heredoc'd script.