Summary
Several CopilotClientOptions are implemented by lowering them to environment variables set on the spawned CLI child process. With the new in-process (FFI) transport (RuntimeConnection.ForInProcess() / Transport::InProcess / forInProcess()), there is no child process the SDK controls the environment of for these values, so these options are not honored in-process. This affects all SDKs adding the in-process transport (.NET already merged; TypeScript and Rust in progress on stevesa/ffi-inproc-rust-ts).
This issue tracks the gap and the fact that the current workaround is to set the corresponding environment variable on the host/parent process yourself before constructing the client.
Why it happens
For stdio/TCP, the SDK spawns a child process and populates startInfo.Environment (or the equivalent), so per-client values are coherent — each client owns its own OS process.
For in-process, the native runtime cdylib is loaded into the shared host process, and host-side native reads consult the ambient host process environment, not a per-client env block. .NET's ValidateEnvironmentOptions therefore rejects Environment/Telemetry for in-process, and the in-process start path passes an effectively empty env to host_start — so the remaining env-lowered options are simply never applied.
Complete list of options lowered to env vars (from dotnet/src/Client.cs)
| Option |
Env var(s) |
In-process behavior today |
Environment |
(replaces whole env block) |
Rejected (throws) |
Telemetry |
COPILOT_OTEL_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, COPILOT_OTEL_FILE_EXPORTER_PATH, COPILOT_OTEL_EXPORTER_TYPE, COPILOT_OTEL_SOURCE_NAME, OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT |
Rejected (throws) |
GitHubToken |
COPILOT_SDK_AUTH_TOKEN (+ --auth-token-env CLI arg) |
Silently ignored |
BaseDirectory |
COPILOT_HOME |
Silently ignored |
Mode == Empty |
COPILOT_DISABLE_KEYTAR=1 |
Silently ignored |
ConnectionToken (TCP only) |
COPILOT_CONNECTION_TOKEN |
N/A (no TCP in-process) |
Note: UseLoggedInUser, SessionIdleTimeoutSeconds, EnableRemoteSessions, LogLevel are lowered to CLI args (--no-auto-login, --session-idle-timeout, --remote, --log-level), not env vars — but the FFI host_start argv is [entrypoint, --embedded-host] and does not forward these either, so they are a related (separate) in-process gap worth confirming.
Current workaround
Set the environment variable on the host/parent process before creating the client, e.g.:
- Auth: export
COPILOT_SDK_AUTH_TOKEN (or ensure ambient GH_TOKEN/GITHUB_TOKEN are valid) instead of GitHubToken.
- Home: export
COPILOT_HOME instead of BaseDirectory.
- Keytar: export
COPILOT_DISABLE_KEYTAR=1 instead of relying on Mode = Empty.
- Telemetry: export the
COPILOT_OTEL_* / OTEL_* vars instead of Telemetry.
Proper long-term fix
These should all become first-class server-level options, not reliant on being passed as env vars. The runtime code can continue to look for env vars as an override for back-compat.
Acceptance / follow-ups
Refs: dotnet/src/Client.cs ValidateEnvironmentOptions (~L215), in-process start (~L339-353), child-process env block (~L2079-2105), ApplyTelemetryEnvironment (~L1971), auth arg (~L2029). Related PRs: #1901, #1920, #1929, #1930.
Other issues
- Windows+Inproc CI legs are disabled for .NET and Rust due to napi-oop cleanup race. This likely doesn't affect real apps but we do need to re-enable these CI legs for Windows and be sure it's robust after removing napi-oop.
- Underlying runtime doesn't close sqlite connections after a forceStop (or possibly even a session disposal). This leaves locked files on Windows. It might be that in cases where sqlite is shut down gracefully it's slow - this is my leading explanation for why the inproc CI legs all run 25-50% longer than the out-of-proc equivalents.
- API changes:
- For 2.0, we should actually remove cwd/env options from CopilotClient across all languages instead of just deprecating. The only way to set those should be on the stdio/tcp transports.
- In the Rust SDK, we've left cwd/env on ClientOptions instead of moving them to transport because it would be a breaking change (language limitation: can't add fields to units/structs that weren't declared #[non_exhaustive] #[derive(Default)]). We should take the breaking change for v2.
- The Rust build.rs is forked between inproc and out-of-proc modes for backcompat, since inproc needs the .node binary and that's not included in the GitHub CLI release (so for inproc we get it from npmjs.org, but that might not be viable long term). We should update the underlying runtime's build/publish mechanism to publish the raw native binaries as separate items on GitHub Releases.
Summary
Several
CopilotClientOptionsare implemented by lowering them to environment variables set on the spawned CLI child process. With the new in-process (FFI) transport (RuntimeConnection.ForInProcess()/Transport::InProcess/forInProcess()), there is no child process the SDK controls the environment of for these values, so these options are not honored in-process. This affects all SDKs adding the in-process transport (.NET already merged; TypeScript and Rust in progress onstevesa/ffi-inproc-rust-ts).This issue tracks the gap and the fact that the current workaround is to set the corresponding environment variable on the host/parent process yourself before constructing the client.
Why it happens
For stdio/TCP, the SDK spawns a child process and populates
startInfo.Environment(or the equivalent), so per-client values are coherent — each client owns its own OS process.For in-process, the native runtime cdylib is loaded into the shared host process, and host-side native reads consult the ambient host process environment, not a per-client env block. .NET's
ValidateEnvironmentOptionstherefore rejectsEnvironment/Telemetryfor in-process, and the in-process start path passes an effectively empty env tohost_start— so the remaining env-lowered options are simply never applied.Complete list of options lowered to env vars (from
dotnet/src/Client.cs)EnvironmentTelemetryCOPILOT_OTEL_ENABLED,OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_EXPORTER_OTLP_PROTOCOL,COPILOT_OTEL_FILE_EXPORTER_PATH,COPILOT_OTEL_EXPORTER_TYPE,COPILOT_OTEL_SOURCE_NAME,OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENTGitHubTokenCOPILOT_SDK_AUTH_TOKEN(+--auth-token-envCLI arg)BaseDirectoryCOPILOT_HOMEMode == EmptyCOPILOT_DISABLE_KEYTAR=1ConnectionToken(TCP only)COPILOT_CONNECTION_TOKENNote:
UseLoggedInUser,SessionIdleTimeoutSeconds,EnableRemoteSessions,LogLevelare lowered to CLI args (--no-auto-login,--session-idle-timeout,--remote,--log-level), not env vars — but the FFIhost_startargv is[entrypoint, --embedded-host]and does not forward these either, so they are a related (separate) in-process gap worth confirming.Current workaround
Set the environment variable on the host/parent process before creating the client, e.g.:
COPILOT_SDK_AUTH_TOKEN(or ensure ambientGH_TOKEN/GITHUB_TOKENare valid) instead ofGitHubToken.COPILOT_HOMEinstead ofBaseDirectory.COPILOT_DISABLE_KEYTAR=1instead of relying onMode = Empty.COPILOT_OTEL_*/OTEL_*vars instead ofTelemetry.Proper long-term fix
These should all become first-class server-level options, not reliant on being passed as env vars. The runtime code can continue to look for env vars as an override for back-compat.
Acceptance / follow-ups
Environment/Telemetrythrow;GitHubToken/BaseDirectory/keytar silently no-op).host_startenv_jsonin host-side reads so per-client env is coherent in-process.UseLoggedInUser, idle timeout, remote, log level) in-process.Refs:
dotnet/src/Client.csValidateEnvironmentOptions(~L215), in-process start (~L339-353), child-process env block (~L2079-2105),ApplyTelemetryEnvironment(~L1971), auth arg (~L2029). Related PRs: #1901, #1920, #1929, #1930.Other issues