Claude Code on Windows: your MCP server is launching the wrong bash.
Your MCP server works on a Mac. On Windows, Claude Code shows Failed to reconnect to <server>: CONNECTION_CLOSED (sometimes -32000). Claude Code itself runs fine, its Bash tool works, Git Bash is installed. And somewhere in a log there is a line that makes no sense on a machine where you never asked for Linux:
Windows Subsystem for Linux has no installed distributions.
Your server was never started by Git Bash. It was started by WSL's launcher. Here is why, how to see it in ten seconds, why the usual PATH fix cannot work, and what does.
Correction, 25 September 2026. The July version of this post told you to append Git\bin to your user PATH so that "anything that spawns bash by name will work". That is wrong, and we proved it on our own machines in August. Our July CI test passed only because it handed the test its own Git Bash, a path no real user has. The rest of this post is what we should have written.
Measured on our Windows machines between 5 and 14 August 2026, including a fresh VM. You do not need WSL for any of this.
Why the terminal works and the server doesn't
Two different things launch bash in Claude Code on Windows, and they find it differently.
- Claude Code's own Bash tool looks for Git Bash itself. If it can't find it, you point it there with
CLAUDE_CODE_GIT_BASH_PATH. This is why your session feels healthy. - An MCP server is started from its
commandfield, directly, with no shell in between. Windows resolves the bare namebashthe way it resolves any program name.CLAUDE_CODE_GIT_BASH_PATHplays no part.
So a server declared as "command": "bash" gets whatever Windows finds first under that name. On the machines we tested, that was never Git Bash.
See it in ten seconds
In PowerShell:
Get-Command bash -All | Select-Object -ExpandProperty Source
What we got:
C:\WINDOWS\system32\bash.exe
C:\Users\<you>\AppData\Local\Microsoft\WindowsApps\bash.exe
C:\Program Files\Git\bin\bash.exe
Line 1 is the WSL launcher, line 2 a WSL store alias, line 3 the Git Bash you meant. The first line is the one that runs. C:\Windows\System32\bash.exe is the WSL launcher. What happens next depends on the machine:
- No Linux distribution installed (most Windows machines): it prints the "no installed distributions" message and exits 1. In a log the text may look spaced out,
W i n d o w s S u b s y s t e m: the launcher writes UTF-16, which is its signature, not Git Bash's. - A distribution installed: WSL's bash receives your script as a Windows path (
C:\Users\...\run.sh), cannot open it, and exits.
Either way the process dies before the MCP handshake, and Claude Code reports a closed connection with no mention of bash or WSL.
Why fixing PATH can't work
The instinct is to put Git Bash on PATH. We did exactly that, and wrote it up as the fix. It cannot work.
A Windows process builds its PATH as the Machine entries first, then the User entries. C:\Windows\System32 is a Machine entry. Anything you append to your User PATH comes after it, so System32\bash.exe wins every time. That's how Windows builds PATH on every machine, not a quirk of yours.
You could prepend Git to the Machine PATH as an administrator. We don't recommend it: it changes what bash means for every program on the machine, and one Windows update or Git reinstall undoes it silently.
The fix, if you write the server
Stop naming bash in command. Launch the runtime your server actually needs, directly:
"my-server": {
"command": "uv",
"args": ["run", "--directory", "${CLAUDE_PLUGIN_ROOT}/server",
"--frozen", "python", "${CLAUDE_PLUGIN_ROOT}/server/main.py"]
}
The same goes for node server.js, or any binary your installer puts on PATH itself. We moved both of our servers from bash run.sh to uv run. With a stand-in that reproduces the WSL failure exactly, the old launch died with our user's error word for word, and the new one booted both servers.
This matters even more for plugins. A plugin's mcpServers entry has command, args and env, and no per-OS variant. One command has to work on macOS, Linux and Windows. bash is the one name that means something different on Windows, so it is the one name you cannot use.
If your wrapper script did useful work (ours added ~/.local/bin and the Homebrew folders to PATH, because Claude Code started from a desktop app may not see them), move that job into the installer or the server. Don't drop it quietly.
The fix, if you only use the server
In your own .mcp.json or user config, replace the bare name with the full path, which Windows does not have to look up:
"command": "C:\\Program Files\\Git\\bin\\bash.exe"
Check where Git is on your machine first. A per-user Git install lives in %LOCALAPPDATA%\Programs\Git, not C:\Program Files\Git.
For a plugin's server this doesn't stick: the plugin cache is replaced on every update. Report it to the author with the output of the Get-Command line above. That output is the whole diagnosis.
Presence is not execution
Why did this survive so long for us? Our checks asked whether bash.exe exists. It does. The WSL launcher is a real, executable file. Our installer's "Git Bash OK" line was green on every machine where the servers were dead.
Two rules we now apply everywhere:
- Check by running, never by finding a file. Launch the exact command and arguments Claude Code will use, from PowerShell, with no bash handed in, and require a real MCP reply. A check that finds
bash.exeproves nothing. - Test what users run, not what CI hands you. A Windows CI step with
shell: bashgives your test Git Bash for free. Your users don't get that. Run the launch test under PowerShell and read the command from your real config.
The same trap exists for python3: on Windows it can be a Microsoft Store shortcut. The file exists, and it opens the Store instead of running Python.
A second trap if you do use Git Bash: two bash.exe files
Git for Windows ships two:
Git\bin\bash.exeis a wrapper. It sets the MINGW64 environment and puts/mingw64/binand/usr/binin front of whatever PATH it receives. We handed itPATH=/tmp/nowhereandopensslwas still found.Git\usr\bin\bash.exeis plain bash. It keeps the PATH you give it.
For running a server this rarely matters. For a test that proves "it fails cleanly when tool X is missing" by removing X from PATH, the wrapper puts X back and your test proves nothing. Use the plain one there.
The other Windows traps we hit
Still true from the July version.
import fcntl kills Python servers at load. fcntl is POSIX-only. At module level, the import fails before your server can speak stdio. Guard it and treat file locks as best-effort:
try:
import fcntl
except ImportError:
fcntl = None
Text encoding. Windows defaults to cp1252. read_text() without an encoding passes on macOS and fails on Windows at the first character outside cp1252. Always pass encoding="utf-8", both ways.
OpenSSH refuses a copied private key. Its permissions come from inherited ACLs, which are too open. Fix them:
icacls <key> /inheritance:r /grant:r "%USERNAME%:R"
Never hardcode C:\Program Files\Git. Per-user installs live elsewhere. Ask where Git is: where.exe git returns ...\Git\cmd\git.exe, and bash.exe sits in the sibling bin folder.
So when do you need WSL?
When your work genuinely needs Linux: Linux-only Docker images, Linux system calls, tools with no Windows build, or sandboxed command execution (Claude Code supports sandboxing on WSL 2, not on native Windows). For running Claude Code and MCP servers, no. You need servers that don't name bash.
This cost us nine days, and we had already published the wrong fix. Klyr exists so an assistant keeps what was learned the hard way, instead of you finding it again on the next machine. If that's a problem you have, get Klyr.
New to Claude Code? Start with our non-developer guide.
FAQ
Why does my MCP server show CONNECTION_CLOSED only on Windows?
If its command is bash, Windows runs C:\Windows\System32\bash.exe, the WSL launcher, not Git Bash. It exits before the MCP handshake. Check with Get-Command bash -All in PowerShell.
I added Git\bin to my PATH. Why is it still broken?
Windows puts Machine PATH entries before User ones, and System32 is a Machine entry. A User PATH entry can never beat it.
Why does Claude Code's Bash tool work if bash is wrong?
Claude Code finds Git Bash for its own Bash tool, or uses CLAUDE_CODE_GIT_BASH_PATH. MCP servers are launched directly from their command field, with no shell, and that setting doesn't apply to them.
How do I fix a server I didn't write?
In your own config, set command to the full path of Git's bash.exe. For a plugin, report it to the author: the plugin cache is replaced on every update.
Do I need WSL to run Claude Code on Windows?
No. Claude Code runs natively. You need WSL only for Linux-only tooling or sandboxed execution.
Why does my Python MCP server crash instantly on Windows?
Often a module-level import fcntl, which is POSIX-only. Guard it with try/except ImportError.