Windows/Git Bash: Bash tool command line truncated at ~8175 chars, and backslash runs halved by MSYS2 argv parsing
Summary
On Windows with Git Bash as the Bash tool shell, two independent defects silently corrupt or truncate commands before bash ever sees them. Both are reproducible and measurable; neither produces an error that points at the real cause.
- The command line is cut at ~8175 characters, which surfaces as
unexpected EOF while looking for matching ...on a command whose syntax is perfectly valid. - Runs of backslashes are halved in transit, so regexes, C/Python string literals, JSON payloads and UNC paths arrive altered, with no error at all.
I found these while investigating a week of "heredoc problems" in one project: 15 incidents over 5 days, 7 of them defect 1. The heredoc was never the cause — it was just the vehicle for large content.
Environment
- Claude Code 2.1.263 (VS Code extension), Windows 11
- Git for Windows bash 5.2.37, MSYS runtime 3.6.6 (
MINGW64_NT-10.0-26200 ... Msys)
Defect 1 — truncation at ~8175 characters
The tool wraps every command roughly as:
bash -c "source <shell-snapshot> 2>/dev/null || true && export TEMP=... TMP=... && shopt -u extglob ... && eval '<command, each single quote rewritten as '\"'\"'>' < /dev/null && pwd -P >| <cwd-file>"
The prologue is ~337 characters. When the assembled line exceeds the Windows limit it is cut mid-command, and the resulting error blames the shell syntax.
Reproduction
Ask the Bash tool to run a command of ~8 KB, e.g. cat > file <<'EOF' with a large body, or simply:
echo <8000 'x' characters>
Result: Exit code 2 / /usr/bin/bash: -c: line N: unexpected EOF while looking for matching '' — while bash -n` on the exact same command returns 0.
Measurement
Bisecting with spawnSync("bash", ["-c", arg]) from node (same shape as the harness):
| quantity | measured |
| --- | --- |
| wire budget | 8175 characters |
| cost per ' in the command | 4.003 (each ' becomes '"'"') |
| cost per " in the command | 0.000 |
This matches the independent estimate from the seven real incidents, whose truncation points all intersect in [8176, 8210].
The practical consequence is that the failure is attributed to the heredoc or to quoting, and the usual "fix" (rewriting the quotes) does nothing. The reliable workaround is to write the body to a file with the Write tool and run the file.
Defect 2 — backslash runs are halved
bash.exe is an MSYS2 program spawned by a non-MSYS parent (node), so the MSYS2 runtime re-parses the Windows command line itself (globify() in dcrt0.cc) and consumes one backslash of every escaped pair inside the quoted argument.
Reproduction
Send this through the Bash tool, inside a quoted heredoc, where bash must not touch backslashes:
cat <<'EOF' | od -c
a\b c\\d e\\\\f
EOF
Expected: a\b c\\d e\\\\f. Actual: a\b c\d e\\f — every run is halved. The transcript JSON confirms the harness received the correct number of backslashes; the loss happens between node and bash.exe.
Same result when emulated directly:
spawnSync("bash", ["-c", String.raw`printf '%s' 'c\\d'`], { encoding: "utf8" })
// stdout: "c\d"
Impact
Anything carrying real escapes is silently altered: sed/grep patterns, printf formats, Python/C source in a heredoc, JSON bodies, UNC paths. There is no error — the command runs and produces subtly wrong output. In one case a \r\n escape written into a file arrived as a real newline and took four attempts to diagnose.
Over 32,184 historical Bash calls in one project, 1.66% contained a doubled backslash.
MSYS=noglob is not a fix
Setting MSYS=noglob also disables the runtime's quote handling, so argv splitting breaks entirely:
c\\d: -c: line 1: unexpected EOF while looking for matching `''
The PowerShell tool and the Write tool are unaffected, because neither goes through MSYS argv parsing.
Suggested direction
Both defects come from passing the command as a Windows command-line argument to an MSYS2 program. Writing the command to a temporary file and running bash <file>, or feeding it on stdin, would avoid the length limit and the argv re-parsing at once. Failing that, surfacing a clear error when the assembled line exceeds the limit would at least stop the failure from masquerading as a syntax error.
Related, but different root cause
These look like the same signature and are worth cross-referencing, though they are caused by an apostrophe in the profile path rather than by length or by MSYS argv parsing: #27163, #28759, #31465, #27212.
The new elements here are the measured ~8175-character cut on syntactically valid commands, and the backslash halving.