Agent Host: clean-env agent spawn on Windows strips APPDATA/ProgramFiles/ComSpec etc., breaking dotnet/NuGet and gh in agent sessions
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
- VS Code Version: 1.134.0 (commit 110a328ea54b42367b803ec53ee0bf52ef26b419, stable, x64)
- OS Version: Windows 11 Education N 10.0.26200
- Agent: Claude via Agents window (Claude Agent SDK 0.3.220 / claude-code 2.1.220)
## Summary
On Windows, shell commands executed by an agent session (Claude, Agents window) run in an environment that is missing core Windows environment variables: `APPDATA`, `LOCALAPPDATA`, `ProgramData`, `ProgramFiles`, `ProgramFiles(x86)`, `ComSpec`, `PATHEXT`, `NUMBER_OF_PROCESSORS`, `PROCESSOR_*`, `OS`, `COMPUTERNAME`, `PUBLIC`, `ALLUSERSPROFILE`.
This breaks standard toolchains inside agent sessions:
- `dotnet build` / `dotnet restore` fail on every project with `NuGet.targets(782,5): error : Value cannot be null. (Parameter 'path1')` — NuGet resolves its config path from `APPDATA`. Aspire's `Aspire.AppHost.Sdk` resolution fails the same way ("Failed to load NuGet settings. Value cannot be null. (Parameter 'path1')").
- `gh` (GitHub CLI) cannot find its keyring credentials/config (stored under `%AppData%\GitHub CLI`) and reports it is not authenticated, even though it is.
Exporting `APPDATA` etc. manually inside the shell command works around it, which the agent has to rediscover in every session.
## Root cause
`out/vs/platform/agentHost/node/agentHostMain.js` builds the environment for the agent runtime spawn from scratch instead of inheriting `process.env`. Deminified:
```js
function buildEnv(clean = true) {
let env = clean
? { ELECTRON_RUN_AS_NODE: "1",
NODE_OPTIONS: undefined,
ANTHROPIC_API_KEY: undefined,
HOME: process.env.HOME,
USERPROFILE: process.env.USERPROFILE,
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1" }
: { ...process.env, ELECTRON_RUN_AS_NODE: "1", NODE_OPTIONS: undefined };
env["AI_AGENT"] = "github_copilot_vscode_agent";
for (const k of Object.keys(process.env))
if (k !== "ELECTRON_RUN_AS_NODE" && (k.startsWith("VSCODE_") || k.startsWith("ELECTRON_")))
env[k] = undefined;
return env;
}
```
The Claude session spawn takes the `clean = true` branch, so only `HOME` and `USERPROFILE` survive from the parent environment. Downstream, the SDK re-adds `PATH`, `TEMP`, `USERNAME`, etc., and the MSYS runtime (Git Bash) synthesizes `SYSTEMROOT`/`WINDIR` when missing (verified with `env -i bash -c env`) — but nothing restores `APPDATA` and the other variables listed above, so every child process of the agent's shell runs without them.
The env-hygiene intent (stripping `VSCODE_*`/`ELECTRON_*`, unsetting `ANTHROPIC_API_KEY`) is sound, but on Windows an allowlist of only `HOME`+`USERPROFILE` is too aggressive: `APPDATA`/`ProgramFiles`/`ComSpec`/`PATHEXT` are not secrets, they are part of the OS contract that virtually every Windows CLI depends on.
## Steps to Reproduce
1. On Windows, open a repo in VS Code and start a Claude agent session from the Agents window.
2. Ask the agent to run `dotnet restore` on any solution (or simply `echo %APPDATA%` / `printenv APPDATA`).
3. `APPDATA` is empty and NuGet fails with `Value cannot be null. (Parameter 'path1')`. Running the same command in the integrated terminal works fine.
## Suggested fix
In the `clean` branch, preserve the standard Windows system/user-profile variables (e.g. `APPDATA`, `LOCALAPPDATA`, `ProgramData`, `ProgramFiles`, `ProgramFiles(x86)`, `CommonProgramFiles*`, `ComSpec`, `PATHEXT`, `SystemRoot`, `SystemDrive`, `windir`, `OS`, `NUMBER_OF_PROCESSORS`, `PROCESSOR_*`, `COMPUTERNAME`, `PUBLIC`, `ALLUSERSPROFILE`, `DriverData`) the same way `HOME`/`USERPROFILE` are preserved — or reuse the inherit branch plus an explicit denylist for secrets.
Related: #316791 (same class of problem — agent session environment missing `GIT_ASKPASS`).
Contributor guide
Assessment
This issue has not been assessed yet.