[BUG] Windows/Git-Bash: harness bash -c wrapper wedges mid-session — every Bash tool command fails with "-c: line 111: unexpected EOF matching" (often silently); direct bash works

Open 💬 0 comments Opened Jun 14, 2026 by LZong-tw

Summary

On Windows + Git Bash, the Bash tool's command wrapper wedges mid-session: from some point on, every Bash tool invocation — even a trivial pwd — fails with

/usr/bin/bash: -c: line 111: unexpected EOF while looking for matching `"'

(exit 2), and produces zero side effects (a printf foo > file.txt never creates the file). Often it surfaces as the even more confusing silent "command completed with no output" with no error at all.

The key fact: **bash itself is fine. The harness's bash -c wrapper is broken. A trivial command like pwd should be bash -c "pwd" (1 line). The error says -c: line 111 — proving the harness prepends ~110 lines of plumbing/preamble around my 1-line command, and that preamble currently contains an unterminated "**, so bash parses to EOF and never reaches my command.

This makes the Bash tool 100% unusable for the rest of the session, frequently silently (no error text), which is far worse than a loud failure — the agent thinks commands "succeeded with no output" and proceeds on false assumptions.

Impact / severity

  • Total, persistent loss of the Bash tool for the remainder of a session once it trips.
  • Silent in the common case (completed with no output, no error) → the agent cannot tell that git commit, test runs, etc. did nothing. This is a correctness/safety hazard, not just an annoyance.
  • No in-tool recovery: dangerouslyDisableSandbox: true does not help (it isn't a sandbox issue), and there is no "reset shell" tool. The only escape is to abandon Bash entirely (switch to the PowerShell tool) or restart the whole session.

Environment

  • Claude Code 2.1.177
  • Windows 11 Pro, build 10.0.26200.0
  • Git for Windows 2.53.0.windows.2, GNU bash 5.2.37(1)-release (x86_64-pc-msys)
  • Shell: Git Bash (the Bash tool); PowerShell 7 tool also available and unaffected

Reproduction evidence (decisive contrast)

All four observations are from the same machine, same session, same moment:

1. Bash tool — broken (live):

# tool call: printf liveprobe > zz_live_probe.txt; echo BASHTOOL_RAN
/usr/bin/bash: -c: line 111: unexpected EOF while looking for matching `"'   (exit 2)
# zz_live_probe.txt is NOT created — zero side effects

Earlier in the same wedged state the identical class of command returned "completed with no output" with no error line at all (silent).

2. Interactive ! bash mode — same failure (shares the same wedged shell):

! pwd
/usr/bin/bash: -c: line 111: unexpected EOF while looking for matching `"'

3. Direct bash from the PowerShell tool — WORKS (proves bash + env are fine):

& "C:\Program Files\Git\bin\bash.exe" -c "echo DIRECT_BASH_OK; pwd"
# DIRECT_BASH_OK
# /c/dev/sugar-dating   (exit 0)

Works with BASH_ENV both set (/etc/bash.bashrc) and explicitly cleared.

4. PowerShell tool — fully functional (this entire diagnosis was performed with it).

Root-cause analysis (what is and isn't the cause)

Ruled out by direct inspection:

  • bash binary — fine (observation 3).
  • /etc/bash.bashrc (BASH_ENV target) — stock MSYS, 60 lines, balanced quotes, unmodified for months. Clearing BASH_ENV changes nothing.
  • The shell snapshot (~/.claude/shell-snapshots/snapshot-bash-*.sh) — 22 lines, clean, all quotes balanced (the rg function + export PATH='...' are correct). Not the source of "line 111".
  • User/project hooks — none of the PreToolUse: Bash hooks rewrite the command (no updatedInput / hookSpecificOutput.command anywhere). They only allow/deny/add-context.

What remains — and is therefore the culprit — is Claude Code's own Bash-tool command-assembly layer: the code that builds the final bash -c "<snapshot-source + cwd-restore + plumbing + USER COMMAND>". Two corroborating facts:

  1. -c: line 111 for a 1-line user command ⇒ ~110 lines of harness-injected preamble around it.
  2. The unterminated " is in that injected preamble (the user command here, printf ... ; echo ..., has perfectly balanced quotes).

Onset is mid-session, not from boot. Bash worked for dozens of commands, then broke — immediately after launching two concurrent run_in_background Bash commands containing quotes + redirection (e.g. codex review --base master -c sandbox_mode="danger-full-access" > /c/.../out.txt 2>&1). The most consistent explanation: the harness drives a persistent bash session, and a backgrounded command with quotes/redirection left the persistent shell's input buffer with an open "; every subsequent command is then appended after that unclosed quote, so bash accumulates lines and only errors at EOF (line 111). The wedge lives in the in-memory persistent shell, which is why every on-disk artifact (bashrc, snapshot) is clean yet the tool is broken, and why a session restart (fresh shell) is the only full reset.

Why this must NOT be closed as a duplicate / "works on my machine"

This is a distinct, reproducible failure fingerprint with a full root-cause writeup, not a vague "no output" report. Please do not fold it into a stale closed issue without addressing the actual mechanism. For the record, it is related to but not covered by:

  • #26481 (set -o onecmd injection, Windows/MINGW no output) — closed; this report adds the -c: line 111: unexpected EOF matching " signature + the mid-session-wedge-from-backgrounded-quoted-command trigger.
  • #57125 (hook command wrapped in bash -c '...', apostrophes brick the session) — closed; here it is the harness's own command wrapper (not a hook command) and it wedges mid-session via the persistent shell, not at a fixed substituted path.
  • #52983 (shell snapshot creation fails) — different: our snapshot is created and clean; the corruption is in the assembly around it.
  • #21915 / #54037 / #19525 / #26545 (Windows Bash tool "no output" / exit 1) — same family, but those were closed as fixed; this is a 2.1.177 recurrence with a precise line 111 reproduction and a clear "persistent-shell got an open quote" hypothesis.

If you believe it duplicates one of the above, please first explain how that issue's fix is supposed to prevent a persistent Git-Bash session from being left with an unterminated " by a backgrounded quoted command, and why a clean snapshot + clean bashrc + working direct-bash still yields -c: line 111: unexpected EOF through the tool. A drive-by "dup, closing" is not an answer to that.

Concrete asks

  1. Detect-and-respawn: when a bash -c exits with a shell parse error (unexpected EOF, unexpected end of file) that does not originate from the user command, treat the persistent shell as corrupt and respawn a fresh one automatically (and retry once), instead of poisoning every subsequent command.
  2. Never fail silently: a non-zero bash -c must always surface stderr to the tool result. The "completed with no output" path (empty result on a parse failure) is the most dangerous part — it makes the agent believe destructive/important commands ran.
  3. Quote/heredoc isolation for backgrounded commands: ensure a run_in_background command with quotes/redirection cannot leave the shared persistent shell's input buffer in an open-quote state (run backgrounded commands in an isolated subshell / separate process so they can't wedge the foreground session).
  4. Debuggability: on a wrapper parse failure, emit (at least under a debug flag) the actual assembled bash -c string so the offending injected line is visible. Right now the user cannot see the 110-line preamble that breaks.
  5. A reset shell / fresh-shell escape hatch exposed to the tool, so recovery doesn't require restarting the entire session.

Workaround (for others hitting this)

  • The PowerShell tool is unaffected — switch all shell work to it. On Windows it's the more reliable shell anyway.
  • To restore the Bash tool specifically, restart the Claude Code session (regenerates a clean persistent shell). Deleting on-disk snapshots does not help — the corrupt state is in memory.
  • Diagnostic to tell "didn't run" from "ran but stdout swallowed": printf marker > probe.txt via the Bash tool, then read probe.txt with a non-Bash tool. If the file is absent, the command body never executed (wrapper parse failure); if present, it's an stdout-capture problem instead.

View original on GitHub ↗