[Bug] Bash tool silently strips backslashes on Windows/Git Bash
Bug Description
Bash tool silently collapses \\ to \ in commands on Windows/Git Bash
## Summary
On Windows, the Bash tool destroys one backslash from every \\ pair in the
command string, somewhere between the stored tool input and the bash argv.
Bash never receives the second backslash, so no amount of correct quoting in
the command can protect the content — including a quoted heredoc (<<'EOF'),
which by POSIX must do zero processing.
This is silent. Writing C# via a heredoc turns "tools\\bench.cmd" into
"tools\bench.cmd". \b is a valid C# escape (backspace), so it compiles
clean and tests pass; the defect surfaces later at runtime. JSON would fail
loudly, C#/regex will not.
## Environment
- Claude Code 2.1.243 (latest at time of report)
- Windows 11 Pro 10.0.26200
- GNU bash 5.3.15(1) (x86_64-pc-cygwin), MINGW64_NT-10.0-26200
- git 2.55.0.windows.3, shell /bin/bash.exe
- permissions.defaultMode: "auto"
## Reproduction
Single Bash tool call:
printf '%s' 'M\\N' > /tmp/bs.txt; od -c /tmp/bs.txt
Expected: M \ \ N
Actual: M \ N
Same result with a quoted heredoc, which should be fully literal:
cat > /tmp/hd.txt <<'EOF'
var p = "tools\\bench.cmd";
EOF
od -c /tmp/hd.txt # -> tools\bench.cmd
## Evidence: chain of custody for one backslash
Three measurement points on the same printf '%s' 'M\\N' call:
| stage | backslashes | measured by |
|----------------------------------------|-------------|------------------------|
| model -> harness, Bash tool input | 2 M\\N | session JSONL via jq |
| model -> harness, Write tool (control) | 2 | session JSONL via jq |
| harness -> bash argv | 1 M\N | /proc/$$/cmdline |
| resulting file | 1 M\N | od -c |
The session transcript holds two backslashes, so the model emitted them and
the harness received them. /proc/$$/cmdline — the literal argv bash was
launched with — holds one. Bash cannot have eaten a byte it never got.
To reproduce the argv half, add to the same call:
cat /proc/$$/cmdline | tr '\0' '\n'
and to reproduce the transcript half:
jq -r '.message.content[]? | select(.type=="tool_use")
| select(.input.command? // "" | contains("bs.txt")) | .input.command' \
~/.claude/projects/<project>/<session-id>.jsonl | od -c
## Signature
- \\ -> \ (collapsed)
- \", \t, \d, \s, lone \ -> unchanged
- \\\\ -> \\ (so exactly one level is stripped)
Independent of quoting: reproduces inside single quotes, inside <<'EOF',
and with no quotes at all.
That pattern looks like un-doubling of escaped Windows path separators being
applied to the entire command string rather than only to paths. The bash
invocation on Windows is wrapped in a Windows-specific preamble
(export TEMP='C:\Users\...' ... && eval '<command>') that handles paths with
single backslashes, which seems the likely home for it — though I've localized
this by measurement, not by reading the code.
## Ruled out
- Bash quoting: reproduces where POSIX guarantees literal handling.
- The eval wrapper: the doubled backslash is already gone in argv, before
eval runs.
- Model-side escaping: the Write tool control in the same session carried
\\ through intact, and the transcript shows the Bash input arrived with
both backslashes.
## Impact
Wider than file writing — any Bash command carrying \\ is affected,
including sed scripts and grep patterns. While investigating, my own
diagnostic grep -oE 'M\\+N' arrived as M\+N and silently matched nothing.
Severity is amplified by defaultMode: "auto", whose standing instruction is
to "make file changes with sed, heredocs, or short scripts, rather than using
the dedicated Read, Edit, or Write tools." That routes file authoring through
the defective path by default. On a C#/JSON codebase this corrupts source
silently and at scale.
Agents cannot work around it: their quoting is already correct, and the bytes
are destroyed after the model is done. The only mitigation is avoiding Bash
for backslash-bearing content, which is in direct tension with auto mode.
Environment Info
- Platform: win32
- Terminal: windows-terminal
- Version: 2.1.243
- Feedback ID: 3eaf988c-6f7d-401b-b35c-32351f301e03
Errors
[]This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗