fix(dotnet): merge env vars instead of replacing inherited environment#1
Closed
PureWeen wants to merge 5 commits into
Closed
fix(dotnet): merge env vars instead of replacing inherited environment#1PureWeen wants to merge 5 commits into
PureWeen wants to merge 5 commits into
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>
…kground tasks (PolyPilot github#299) Before this fix all four SDKs' sendAndWait/SendAndWaitAsync/send_and_wait/SendAndWait resolved immediately on ANY session.idle event, even when backgroundTasks.agents[] or backgroundTasks.shells[] was non-empty — indicating background agents/shells are still running and the session is not truly idle. Fix: only resolve when backgroundTasks is absent or both arrays are empty. Changed files: - nodejs/src/session.ts: check bgTasks.agents.length / bgTasks.shells.length - dotnet/src/Session.cs: check bgTasks.Agents.Length / bgTasks.Shells.Length - python/copilot/session.py: check len(bg_tasks.agents) / len(bg_tasks.shells) - go/session.go: check len(bgTasks.Agents) / len(bgTasks.Shells) Tests: added 4 Node.js unit tests in test/client.test.ts that reproduce the bug (tests 2+3 fail before fix, pass after): - resolves immediately when session.idle has no backgroundTasks (baseline) - does NOT resolve when session.idle reports active background agents [bug repro] - does NOT resolve when session.idle reports active background shells - resolves when session.idle has empty backgroundTasks arrays Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Owner
Author
|
Closing in favor of a clean PR from fix/env-merge-semantics that contains only the env-fix changes (no session.idle contamination). |
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.
Summary
Fixes the bug reported in PureWeen/PolyPilot#441 (the
PureWeen/PolyPilotissue tracker, notgithub/copilot-sdk).In the .NET SDK,
CopilotClientOptions.Environmentsilently clears and replaces the entire child process environment instead of merging user-specified keys with the inherited environment.Problem
When a .NET embedder sets
Environmentto augment PATH or add a few variables, the SDK callsstartInfo.Environment.Clear()(Client.cs:1151), 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):
Cross-SDK Comparison
{ ...process.env, ...options.env })dict(os.environ); env.update(cfg.env))options.Envoros.Environ())append(os.Environ(), ...)ProcessStartInfo.Environmentstarts pre-populatedTesting
EnvironmentTestsprove merge semantics via public API only (StartAsync+PingAsync)E2ETestContext.GetEnvironment()), so behavior is unchanged for existing callersReferences
PureWeen/PolyPilotrepo, notgithub/copilot-sdk