Repository navigation
[Tracking] In-process (FFI) items to be cleaned up #1934
Description
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 ambientGH_TOKEN/GITHUB_TOKENare valid) instead ofGitHubToken. - Home: export
COPILOT_HOMEinstead ofBaseDirectory. - Keytar: export
COPILOT_DISABLE_KEYTAR=1instead of relying onMode = Empty. - Telemetry: export the
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
- Decide, cross-SDK, whether each env-lowered option should be honored-for-worker, rejected, or documented-as-unsupported in-process (currently inconsistent:
Environment/Telemetrythrow;GitHubToken/BaseDirectory/keytar silently no-op). - Runtime: consume
host_startenv_jsonin host-side reads so per-client env is coherent in-process. - Confirm CLI-arg-lowered options (
UseLoggedInUser, idle timeout, remote, log level) in-process.
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.
- Rust has
programandextra_argsonClientOptions, unlike the other languages which only have this on the child-process transport. These options make no sense outside child-process transport, so we should move them as a breaking change. - The Python SDK still does PATH resolution (see commit 05dd60e). We should remove this and require bundling or specifying a path explicitly for child-process transports.
- Rust and Go both have build-time flags that control whether the inproc binary is embedded in the app bundle or not (because it duplicates the copilot SEA binary that's also embedded). Once the Node dep is gone we should get to the point where we embed a minimal bootstrapping executable and the dylib, then we don't need to vary what gets embedded at compile time.
- For most SDKs, the base class/interface common to stdio and tcp is called "child process" transport, which seems like a misnomer since it includes unrelated-process TCP. We should rename this to "out of process" transport. In the case of unrelated-process TCP it can reject path/args at runtime.
- Rust's child proc env option is inconsistent with other languages - the others all treat a nonempty set as meaning "replace the entire env with this set" whereas Rust treats it as "add these to the existing env set".
- Python has both
copilot.generated.rpcandcopilot.rpc. The latter is the same as the former except it excludes internal things. So we should renamecopilot.generated.rpctocopilot._generated.rpcand require people only to usecopilot.rpc.
Activity
github-actions commentedon Jul 7, 2026
Investigation findings
I confirmed this is a genuine bug by examining
dotnet/src/Client.csanddotnet/src/FfiRuntimeHost.cs.What I found
ValidateEnvironmentOptions(called at construction, ~L177) correctly throws forEnvironmentandTelemetrywhen using in-process transport — users get clearArgumentExceptions with helpful messages.StartAsyncin-process path (~L339–353) builds anffiEnvironmentdict from_options.EnvironmentandApplyTelemetryEnvironment, then passes it toFfiRuntimeHost.Create. But since validation already threw for those two options,ffiEnvironmentis always an empty dict for in-process. Crucially, the three remaining env-lowered options are never added to that dict:GitHubTokenstartInfo.Environment["COPILOT_SDK_AUTH_TOKEN"](~L2082)BaseDirectorystartInfo.Environment["COPILOT_HOME"](~L2092)Mode == EmptystartInfo.Environment["COPILOT_DISABLE_KEYTAR"] = "1"(~L2101)CLI-arg-lowered options (
--no-auto-login,--session-idle-timeout,--remote,--log-level) are also not forwarded to the FFIhost_startargv, which is hardcoded to[entrypoint, "--embedded-host"].There's also a stale comment at ~L343–344 that says "The worker reads its configuration from the environment passed here, so apply the same telemetry-derived vars..." — but telemetry is rejected at construction and the three auth/home/keytar vars are never applied, so the comment doesn't reflect actual behavior.
Why it's a bug
The inconsistency is the bug:
EnvironmentandTelemetryfail loudly (good), butGitHubToken,BaseDirectory, andMode == Emptysilently no-op. A caller has no indication that auth token configuration was not applied to the in-process runtime.The workaround documented in the issue is correct: set
COPILOT_SDK_AUTH_TOKEN,COPILOT_HOME, andCOPILOT_DISABLE_KEYTAR=1directly on the host process environment before constructing the client.Labelling as bug and linking to the referenced PRs (#1901, #1920, #1929, #1930) for context on the in-process transport work.
TS: align in-process (FFI) transport with .NET behavior
TS: skip in-process-unsupported E2E tests so the inprocess cell is green
Rust: full in-process (FFI) parity with .NET/TS
TS: align in-process (FFI) transport with .NET behavior
TS: skip in-process-unsupported E2E tests so the inprocess cell is green
Rust: full in-process (FFI) parity with .NET/TS
TS: align in-process (FFI) transport with .NET behavior
TS: skip in-process-unsupported E2E tests so the inprocess cell is green
Rust: full in-process (FFI) parity with .NET/TS
Add in-process (FFI) transport for the Node.js SDK
[-]In-process (FFI) transport: options lowered to env vars (auth token, COPILOT_HOME, keytar, telemetry) are not honored in-process[/-][+][Tracking] In-process (FFI) items to be cleaned up[/+]Fix Go FFI e2e CI failures (inprocess matrix)
Solution for github#1934: [Tracking] In-process (FFI) items to be cle…
Solution for github#1934: [Tracking] In-process (FFI) items to be cle…
SteveSandersonMS commentedon Sep 4, 2026
Superseded by #2522 and its v2 sub-issues. All remaining work from this tracker is carried forward as follows:
The original items remain planned v2 work under #2522; closing this issue only consolidates tracking.
SteveSandersonMS commentedon Sep 4, 2026
Correction to the closure mapping above: #2526 was subsequently removed because maintainers will coordinate integration directly. Superseded by #2522 and its v2 sub-issues, all information and remaining work from this tracker is carried forward as follows:
host_start/env_jsonrequirement, client/transport API boundaries, Rust process options and environment semantics, out-of-process naming, and first-class runtime configuration: [v2] Redesign process and transport configuration APIs #2523build.rs/npm constraint, raw native GitHub Release assets, PythonPATHresolution, Rust/Go embedding flags, duplicate payload concern, and minimal-bootstrap target: [v2] Consolidate runtime discovery, acquisition, and embedding #2524napi-oopcleanup race, teardown reliability, SQLite file locking and closure, and the 25–50% shutdown-performance hypothesis: [v2] Complete in-process lifecycle and platform reliability work #2525Maintainers will coordinate integration with remaining v1 releases directly rather than through a separate issue.
The original items remain planned v2 work under #2522; closing this issue only consolidates tracking.