[BUG] Windows desktop: stdio MCP servers get a ~16-var sanitized environment; missing ProgramData breaks ssh silently, missing COMPUTERNAME breaks Get-VM with a misattributed error
Summary
On Windows, Claude Code (desktop app) launches stdio MCP servers with a sanitized environment allowlist of ~16 variables rather than the user's environment. Values set in the server's own env block do arrive correctly — the problem is that the inherited base is reduced, silently and undocumented.
Two of the dropped variables are ones Windows tooling reads implicitly, so the resulting failures name nothing related to the environment. Both took a Process Monitor capture to diagnose, because the exit codes and error messages actively point elsewhere.
| Dropped var | Breaks | Symptom |
|---|---|---|
| ProgramData | Win32-OpenSSH ssh.exe | exits 255 with zero output, even under -vvv |
| COMPUTERNAME | PowerShell Get-VM (Hyper-V) | Get-VM: Value cannot be null. (Parameter 'name') |
The Get-VM one is actively misleading: the null name is the computer name (Get-VM defaults -ComputerName to $env:COMPUTERNAME), but it reads as a missing/invalid VM name in the caller's own config, sending you to debug the wrong file entirely.
Environment
- Claude Code
2.1.240, Windows desktop app - Windows 11 Pro 26200
- stdio MCP server (a Rust binary using
tokio::process::Command, but the mechanism is server-agnostic)
Evidence
Process Monitor capture of the MCP server's child process. This is the complete environment block the child received (hostname/username redacted, PATH trimmed to relevant entries):
Parent PID: 100580
Command line: "C:\Windows\System32\OpenSSH\ssh.exe" -i <key> -o BatchMode=yes user@host bash -ls
Current directory: C:\windows\system32\
Environment:
APPDATA=C:\Users\<USER>\AppData\Roaming
HOMEDRIVE=C:
HOMEPATH=\Users\<USER>
LOCALAPPDATA=C:\Users\<USER>\AppData\Local
LOGONSERVER=\\<HOSTNAME>
PATH=C:\Program Files\nodejs;C:\Program Files\Git\cmd;C:\windows\system32;C:\windows;...
PROCESSOR_ARCHITECTURE=AMD64
PROGRAMFILES=C:\Program Files
SYSTEMDRIVE=C:
SYSTEMROOT=C:\windows
TEMP=C:\Users\<USER>~1\AppData\Local\Temp
USERDOMAIN=AzureAD
USERNAME=<USER>
USERPROFILE=C:\Users\<USER>
VM_REMOTING_SSH=C:\Windows\System32\OpenSSH\ssh.exe <-- from the server's own env block, arrives fine
WINDIR=C:\windows
Notably absent, all present in a normal Windows session: ProgramData, ALLUSERSPROFILE, COMPUTERNAME, PATHEXT, COMSPEC, PUBLIC, OS, SESSIONNAME, NUMBER_OF_PROCESSORS, ProgramFiles(x86), ProgramW6432, TMP. A normal environment has 50+ variables; this has 16.
Reproduction
Reproduce the captured environment exactly with env -i, then add back one variable at a time. Only ProgramData matters:
=== 1. procmon environment, verbatim ===
procmon-env (baseline) exit=255 <NO OUTPUT>
=== 2. add back one stripped var at a time ===
+ ProgramData exit=0 Linux 7.0.0-1012-azure
+ ALLUSERSPROFILE exit=255 <NO OUTPUT>
+ COMPUTERNAME exit=255 <NO OUTPUT>
+ PATHEXT exit=255 <NO OUTPUT>
+ COMSPEC exit=255 <NO OUTPUT>
+ SESSIONNAME exit=255 <NO OUTPUT>
+ PUBLIC exit=255 <NO OUTPUT>
+ TMP exit=255 <NO OUTPUT>
Minimal standalone repro of each failure — no MCP server needed, just a stripped environment:
# ssh: silent 255. Adding ProgramData=C:\ProgramData makes it exit 0.
env -i SYSTEMROOT='C:\windows' USERPROFILE='C:\Users\<USER>' PATH='C:\windows\system32' \
'C:\Windows\System32\OpenSSH\ssh.exe' -o BatchMode=yes user@host true; echo "exit=$?"
# Get-VM: null-name error. Adding COMPUTERNAME=<HOSTNAME> makes it return State.
env -i SYSTEMROOT='C:\windows' USERPROFILE='C:\Users\<USER>' PATH='C:\windows\system32;C:\Program Files\PowerShell\7\' \
pwsh -NoProfile -NonInteractive -Command 'Get-VM -Name "SomeVM"'
Verified the fix end-to-end through the MCP server: with ProgramData and COMPUTERNAME added to the server's env block, ssh returns exit=0 and Get-VM resolves the VM correctly (now reporting the honest The virtual machine ... is not in running state. instead of the null-name error).
Expected behavior
One of:
- MCP servers inherit the user's full environment, as they do when launched from a terminal — this is what server authors and users assume, and what makes a server behave the same under Claude Code as under any other host.
- If the reduced environment is deliberate (sandboxing, reproducibility), then add the variables Windows tooling depends on implicitly — at minimum
ProgramData,ALLUSERSPROFILE,COMPUTERNAME,PATHEXT,COMSPEC— since without them a large class of ordinary Windows binaries and PowerShell cmdlets fail. - At absolute minimum, document the allowlist. Right now nothing indicates the environment is reduced, and the failures it produces are silent (
ssh) or misattributed to the user's own config (Get-VM).
Workaround
Hardcode the missing variables in the per-server env block:
"my-server": {
"type": "stdio",
"command": "my-server",
"env": {
"ProgramData": "C:\\ProgramData",
"ALLUSERSPROFILE": "C:\\ProgramData",
"COMPUTERNAME": "YOUR-HOSTNAME"
}
}
${VAR} expansion is not a workaround — it resolves against the host's environment, which is the one already missing these variables.
Two things that made this harder to pin down, worth noting for anyone else hitting it:
- The server is spawned once at session start, so config edits need a restart — and a restart can leave previous server processes alive and still serving calls. I had six
my-server.exeprocesses running simultaneously, and calls were landing on stale ones with the old environment, which made a correct fix look like it had failed. - Because
env-block values do pass through, the natural conclusion is "env works fine, must be something else" — which is exactly wrong, and cost several hours.
Related
- #84314 —
${VAR}expansion for MCP env non-deterministic on Linux. Different mechanism, same general area (what environment MCP servers actually see).