Skip to content

[BUG] Windows: Bash tool commands are silently truncated at ~8,181 chars of the bash.exe -c argument, and every \\ is halved #92543

Description

@bilbospocketses

What happens

On Windows the Bash tool passes the whole command to Git's bash.exe as a single -c argument:

bash.exe -c "source <snapshot> && export TEMP=... && eval '<command, each ' as '"'"'>' && pwd -P >| <cwdfile>"

(That's /proc/$$/cmdline from inside the tool's shell.) Two things go wrong at the node → bash.exe argv hand-off, and I can reproduce both with plain Node spawning bash.exe directly, so this isn't the wrapper — it's the transport:

  1. The -c argument is cut off between 8,181 and 8,190 characters. Bash then reports unexpected EOF while looking for matching '`` (or here-document ... delimited by end-of-file) and nothing runs. Because the wrapper rewrites every `'` as `'"'"'`, the line number in the error is just the last `'` in the user's text before the cut, which sends you hunting for a quoting bug that doesn't exist. libuv only refuses at 32,767 (`ENAMETOOLONG`), so anything between ~7.5K and 32K chars fails this way with no diagnostic pointing at length.
  2. Every doubled backslash arrives halved. A\\B reaches bash as A\B, G\\\\H as G\\H — inside a quoted heredoc too. libuv quotes the argument by MS-CRT rules (a backslash is only doubled before a "), while the MSYS2 runtime parses \\ inside a double-quoted argument as an escape. Writing the same script to a file and running the file is byte-exact.

A census of my own transcripts (7,815 Bash commands, 37 sessions) shows 0 truncation failures under 8K expanded chars and 25 of 29 at 9K and above; plus a handful of SyntaxError: unterminated string literal in Python heredocs that were the halving.

Repro (no Claude Code needed)

// node repro.js  -- Node 24.19, Git for Windows 2.55.0.windows.3
const { spawnSync } = require('node:child_process');
const bash = 'C:\\Program Files\\Git\\usr\\bin\\bash.exe';   // same result with bin\bash.exe
for (const n of [8100, 8180, 8190, 8200, 9000]) {
  const script = 'echo START; : ' + 'a'.repeat(n - 24) + '; echo END';
  const r = spawnSync(bash, ['-c', script], { encoding: 'utf8' });
  console.log(script.length, JSON.stringify(r.stdout.trim()), r.stderr.trim().slice(0, 60));
}
// 8100 "START\nEND"
// 8180 "START\nEND"
// 8190 "START"          <- tail gone, no error
// 8200 "START"
// 9000 "START"
const r = spawnSync(bash, ['-c', "printf '%s\\n' 'A\\\\B G\\\\\\\\H' | /usr/bin/od -c"], { encoding: 'utf8' });
console.log(r.stdout);   // A \ B G \ \ H   <- sent A\\B G\\\\H

Through the Bash tool the ceiling for the user's command is lower — roughly 7,000 chars / 100 lines — because the wrapper text and the '"'"' expansion share the same 8K budget.

Expected

Either the command runs, or the tool says it's too long. Today it does neither: the command is silently truncated and bash reports a quoting error inside the body.

Suggested fix

On Windows, hand the script to bash via stdin or a temp file (bash <file> / bash -s < file) instead of as a -c argument — that removes both the length cap and the backslash re-interpretation. Failing that, refuse commands whose assembled -c argument exceeds ~8,100 chars with a clear message.

Environment

  • Claude Code 2.1.263, Windows 11 Enterprise 10.0.26200
  • Node v24.19.0 (nvm4w), Git for Windows 2.55.0.windows.3 (bin\bash.exe wrapper → usr\bin\bash.exe, MSYS2 bash 5.3.15)
  • Same behaviour with CLAUDE_CODE_GIT_BASH_PATH unset (default discovery)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:bashbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:windowsIssue specifically occurs on Windows

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions