Windows: the shell configured in CLAUDE_CODE_GIT_BASH_PATH is never spawned; a discovered bash is used instead

Status Open
Reported on v2.1.270
Maintainer reply None cached
Activity 0 comments · opened Sep 14, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Not a duplicate — the one-line test

In this issue, nothing fails. Every command resolves, no syntax errors, no command not found, no visible window. The shell simply is not the one I configured, and it is slower.

That single fact separates it from every issue it keeps getting matched against. Those are all about a shell misbehaving. This is about the wrong binary being spawned in the first place.

| Issue | What goes wrong there | Present in my issue? |
|---|---|---|
| #72728 | Snapshot captures Windows-form PATH; bash splits on drive-letter colons; ls: command not found | No. All commands resolve. Snapshot PATH is well-formed. |
| #50541 | PATH with parentheses injected unquoted; syntax error near unexpected token '(' | No. No parse errors of any kind. |
| #91264 / #64163 | A visible console window appears and steals focus per tool call | No. This would still be a bug if no window ever appeared. I added measurements to #91264 separately. |
| #92711 | CLAUDE_CODE_USE_POWERSHELL_TOOL is undocumented | No. That variable was not set for any measurement below. |

Why these keep matching: this report says "PATH" often, because PATH discovery is the mechanism by which the wrong shell gets chosen. But the defect is not in PATH's contents, format, or quoting. PATH here is well-formed and working correctly. The defect is that it is consulted at all, ahead of an explicit configuration variable whose entire purpose is to override it.

The ordering argument, which settles it: Claude Code must choose which bash.exe to spawn before it can generate a snapshot for it or inject anything into it. This defect is strictly upstream of #72728 and #50541. Fix either of those and this one is exactly as broken, because CLAUDE_CODE_GIT_BASH_PATH would still lose to discovery.

Environment

| | |
|---|---|
| Claude Code | 2.1.270 |
| OS | Windows 10 Pro 19045, x64, 12 core |
| Launched via | Windows Terminal -> cmd.exe -> claude.exe |
| Config | Default. No CLAUDE_CODE_USE_POWERSHELL_TOOL, no settings.json shell overrides. |

Summary

CLAUDE_CODE_GIT_BASH_PATH is set to a valid, resolvable bash. Claude Code searches PATH first, finds a different bash, and uses that. The variable is never consulted.

The variable exists to override discovery. Discovery wins instead.

On this machine that costs +10.5 ms on every Bash tool call from running a slower shell than the configured one. On many Windows machines the consequence is worse than slow, because of what usually sits first on PATH (below).

Reproduction

Set CLAUDE_CODE_GIT_BASH_PATH to a valid bash that is not on PATH. Start a new session. From the Bash tool:

powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object Name -match 'bash' | Select ProcessId, ExecutablePath"

Expected

ExecutablePath matches CLAUDE_CODE_GIT_BASH_PATH.

Actual

It matches a PATH candidate. On my machine:

bash.exe on the Windows PATH, in search order:
  1. C:\WINDOWS\System32          <- WSL launcher stub
  2. F:\Tools\PortableGit\bin

CLAUDE_CODE_GIT_BASH_PATH   f:\Koliada\Tools\Bash\dist\Bash.exe
Claude Code spawned         F:\Tools\PortableGit\bin\..\usr\bin\bash.exe     (PATH #2)

Reproduced across multiple sessions and multiple days.

Cost

Startup of each candidate, .NET Stopwatch around a direct spawn, n=40, minimum, warm:

| Shell | Startup | |
|---|---:|---|
| Configured shell (never selected) | 12.74 ms | |
| cmd.exe (reference point) | 13.90 ms | |
| Git Bash (PATH #2, selected) | 23.22 ms | +10.5 ms per call |
| WSL stub (PATH #1) | ~96 ms | 7.5x, and see below |

Why PATH candidate #1 makes this more than a performance bug

C:\WINDOWS\System32\bash.exe is the WSL launcher. It ships on every Windows machine whether or not WSL is usable, and System32 sorts near the front of the default PATH. So on a default install it is ranked above any shell the user explicitly configured.

  • With no WSL distro installed, it exits with no output. From Claude Code's side that is indistinguishable from a shell that ran and printed nothing.
  • With a distro installed but stale drive mappings present, it writes warnings to stderr on every invocation. On my machine every call emits five lines of wsl: Failed to translate 'X:\...', which contaminates anything captured from that shell, including the generated shell snapshot.

So the ordering does not merely pick a slower shell. It ranks a stub that on many machines either fails silently or corrupts captured output above the shell the user asked for.

Expected behaviour

Claude Code should run the shell I explicitly asked for.

  1. CLAUDE_CODE_GIT_BASH_PATH, when set and resolvable, is the shell that runs. PATH discovery is the fallback for when it is unset or does not resolve, not the first choice.

Observable: ExecutablePath of the spawned bash.exe equals CLAUDE_CODE_GIT_BASH_PATH.

  1. When the fallback is taken, say which shell was chosen somewhere the user can see it.

Related: selection failures are silent

Contributing to how long this took to find:

  • There is no way to ask which shell Claude Code selected. I could only determine it by sampling the Windows process table during a live tool call and reading the child's command line.
  • A second session on this machine has a completely non-functional Bash tool: every call returns Exit code 1 or 127 with no stderr and no message. echo, a builtin that cannot fail, produces nothing.
  • An older debug log records Shell snapshot creation failed with a 10 second timeout and SIGTERM, after which every Bash call fails in ~55 ms with no stderr. Recorded internally, never surfaced.

A single line naming the selected shell at session start would have saved a day here.

What I have not established

  • The cause of the dead Bash tool in my second session is not established. I eliminated the per-call wrapper, the binary itself, the variable's path form, output redirection under a fresh console, -c -l option parsing, and declare -f, each with a control that could have failed. It remains open and I am not claiming it is caused by this defect.
  • Timings are one machine, warm, unpinned, n=40 minimum, every run gated on a control that could fail.
  • Windows process startup here is roughly 2x parallel (about 22 ms of CPU inside 11 ms of wall), so on a loaded or low-core machine these figures get worse, not better.

What Should Happen?

Claude Code should run the shell I explicitly asked for.

  1. CLAUDE_CODE_GIT_BASH_PATH, when set and resolvable, is the shell that runs. PATH discovery is the fallback for when it is unset or does not resolve, not the first choice.

Observable: ExecutablePath of the spawned bash.exe equals CLAUDE_CODE_GIT_BASH_PATH.

  1. When the fallback is taken, say which shell was chosen somewhere the user can see it.

Error Messages/Logs

Steps to Reproduce

https://claude.ai/code/artifact/c08279ea-2ebb-4a4d-9940-5ae5e3c82b94

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.270 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Windows Terminal

Additional Information

https://claude.ai/code/artifact/c08279ea-2ebb-4a4d-9940-5ae5e3c82b94

View original on GitHub ↗