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
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:
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/perl patterns 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.
Summary
The Bash tool collapses the two-character sequence
\\into a single\in thecommand string before the shell parses it. This happens inside single quotes,
inside double quotes, and inside a quoted heredoc (
<<'EOF') alike, so the POSIXguarantee that single quotes preserve backslashes literally does not hold.
It is not a general escape-decode —
\npasses through untouched. It is specificallythe two-backslash sequence.
The failure is silent: a mangled backslash escape usually remains valid regex,
so
sed/grep/awkmatch something different, exit 0, and report success.Environment
Only tested on Windows/Git Bash. Whether macOS and Linux shells are affected is
untested and worth checking.
Minimal reproduction
Expected:
a\\b(single quotes are literal in POSIX sh)Actual:
a\bObserved mapping, across every quoting form including quoted heredocs:
\\\\\← 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):Then compare:
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:The author intends a literal backslash. sed instead receives
a\b, where\bisGNU 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 -iandperl -icalls 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.
awkis the only tool that hints at it, warning:Impact
Any Bash-tool command that needs a literal backslash is affected, most commonly:
sed/grep/awk/perlpatterns matching literal backslashes\\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
\\\\to get\\.PAT=$(printf '\134\134'),which sidesteps the transport entirely.
Suggested fix
Stop unescaping
\\in the command string, or document the transport's escapingcontract explicitly. Failing that, surfacing a warning when a command contains
\\would at least make the corruption visible rather than silent.