fix: .NET Environment.Clear() wipes inherited env vars + cross-SDK merge alignment#3
Open
fix: .NET Environment.Clear() wipes inherited env vars + cross-SDK merge alignment#3
Conversation
The Environment property in CopilotClientOptions was calling startInfo.Environment.Clear() before populating user-provided vars, wiping ALL inherited environment variables (PATH, SystemRoot, TEMP, COMSPEC, etc.) when even a single override was specified. This caused the Node.js-based CLI subprocess to crash on Windows because it requires system env vars that are only available in the inherited environment. Fix: remove the Clear() call so that user-provided entries are merged into the inherited environment (keys are overridden, all others remain). Also update documentation in Types.cs and README.md to clearly describe the merge-override semantics (consistent with how Go and Python document their Env / env options). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…js full-replacement Add regression tests for Issue github#441 (Environment.Clear() bug in .NET SDK). ## dotnet/test/EnvironmentTests.cs (8 new tests) Proves the merge-vs-replace fix works through public APIs only: 1. Environment_DefaultsToNull - documents the API contract 2. Should_Start_When_Environment_Is_Null - baseline: null env works 3. Should_Start_When_Environment_Is_An_Empty_Dictionary - empty dict must NOT wipe inherited vars (before fix: Clear() was still called on empty dict) 4. Should_Start_When_Environment_Has_One_Custom_Key - CANONICAL regression: before the fix this test threw IOException because PATH/SystemRoot were wiped; after the fix the CLI starts with all inherited vars intact 5. Should_Start_When_Environment_Has_Multiple_Custom_Keys - N keys, all merged 6. Should_Start_When_Environment_Overrides_An_Inherited_Key - override PATH 7. TestHarness_GetEnvironment_Pattern_Works_After_Fix - documents why E2E tests never caught the bug (harness always passed full env) 8. Should_Strip_NODE_DEBUG_Even_When_Environment_Is_Null - NODE_DEBUG removal ## nodejs/test/client.test.ts (6 new tests in 'env option' describe block) Documents Node.js SDK's intentionally different semantics: - Node.js uses full-replacement (env: dict replaces process.env entirely) - .NET uses merge-override (Environment: dict merges into inherited env) - Both are correct for their respective runtimes - Node.js never had the bug because spawn env: is always full-replacement Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…docs
All four SDKs now document and implement the same ergonomic contract:
providing a partial env dict ADDS/OVERRIDES those keys while keeping
PATH, HOME, and all other inherited variables intact.
## Node.js (nodejs/src/client.ts + types.ts + README.md)
Changed:
const effectiveEnv = options.env ?? process.env;
To:
const effectiveEnv = options.env ? { ...process.env, ...options.env } : process.env;
A partial dict like { COPILOT_API_URL: '...' } now merges into
process.env instead of replacing it entirely. The test-harness pattern
of { ...process.env, KEY: val } continues to work unchanged.
Updated JSDoc for CopilotClientOptions.env with merge semantics and example.
Added env option to the README options table (it was missing).
## Python (python/copilot/client.py + README.md)
Changed _start_process to:
env = dict(os.environ)
if cfg.env is not None:
env.update(cfg.env)
Applied the same merge at the CLI-path-lookup site (effective_env).
Updated the ClientConfig.env docstring with merge semantics and example.
## Go (go/types.go + README.md) - docs only
Go's Env []string uses full-replacement semantics (matching exec.Cmd.Env).
The comment and README now explicitly document this, note the difference
from the other three SDKs, and show the correct idiom:
env := append(os.Environ(), 'KEY=value')
Go behavior is unchanged; callers already need to pass os.Environ() when
they want partial overrides, and the type makes that clear.
## Tests (nodejs/test/client.test.ts)
Updated the 'env option' describe block to reflect the new merge semantics:
- Renamed 'stores provided env as-is' → 'merges provided env keys into
process.env' with assertions proving the merged object differs from
the input dict and preserves the PATH key.
- Added a new test: 'starts and pings when env is a partial dict with one
custom key' -- this is the canonical merge regression test for Node.js.
- Updated block comment from 'full-replacement' to 'merge semantics'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- python/copilot/client.py: fix docstring example to use SubprocessConfig instead of ClientConfig (which doesn't exist) - nodejs/src/client.ts: remove misleading comment suggesting a fully-isolated env is possible via merge semantics - nodejs/README.md: align env type to Record<string, string | undefined> to match CopilotClientOptions and document that undefined unsets an inherited variable - dotnet/test/EnvironmentTests.cs: rename test from Should_Strip_NODE_DEBUG_Even_When_Environment_Is_Null to Should_Strip_NODE_DEBUG_When_Environment_Dict_Is_Provided to accurately describe what the test does (it passes a non-null Environment dict) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fixes PureWeen/PolyPilot#441.
In the .NET SDK,
CopilotClientOptions.Environmentsilently clears and replaces the entire child process environment instead of merging user-specified keys with the inherited environment.When a .NET embedder sets
Environmentto augmentPATHor add a few variables, the SDK callsstartInfo.Environment.Clear()(Client.cs), wiping all inherited platform variables (COMSPEC,SystemRoot,TEMP,LOCALAPPDATA, etc.). On Windows this breaks ConPTY shell spawning — the CLI's PowerShell tool fails withFile not found: (empty path)because the shell executable can't be resolved without these variables.Before (broken):
Fix
Remove the
Environment.Clear()call. In .NET,ProcessStartInfo.Environmentis pre-populated with the current process's environment, so iterating through the user-provided keys naturally merges (overrides specified keys, inherits the rest).After (fixed):
This PR also aligns Node.js and Python to the same merge semantics, and documents Go's different behavior (full replacement by design).
Cross-SDK Comparison
ProcessStartInfo.Environmentis pre-seeded{ ...process.env, ...options.env })dict(os.environ); env.update(cfg.env))options.Envoros.Environ())append(os.Environ(), ...)Testing
EnvironmentTestsin .NET prove merge semantics via public API only (StartAsync+PingAsync)References