Add in-process (FFI) transport to the Go SDK - #1976
Conversation
Bring the Go SDK to parity with the .NET, Rust, TypeScript, and Python
SDKs by adding an in-process transport that hosts the runtime via the
native FFI library (runtime.node) instead of spawning a child process
for the runtime server.
- ffihost: pure-Go FFI host using ebitengine/purego (CGO stays off),
C ABI binding, single-load-per-process guard, and cdylib resolution
(flat name next to the CLI, or prebuilds/<platform>/runtime.node),
including musl and Windows loaders.
- API: new InProcessConnection{Path, Args} (Experimental) plus
connection-level Env on Stdio/TCP via a shared childProcessConnection
interface.
- client: startInProcess wiring into Start/Stop/ForceStop/killProcess
with no-PATH entrypoint resolution, --no-auto-update, and host
ownership before the blocking handshake for cancellation safety.
validateEnvironmentOptions rejects Env/WorkingDirectory/Telemetry
in-process and env set in both client and connection for child
transports.
- bundler + embeddedcli: extract and embed runtime.node (conditional
per platform) and install it next to the CLI binary with SHA-256
verification; add RuntimeLibPath().
- e2e: COPILOT_SDK_DEFAULT_CONNECTION=inprocess harness support that
swaps only the default stdio connection, mirrors the merged env and
cwd onto the process, and leaves TCP/URI/custom-stdio/telemetry tests
on their transport; new in-process ping smoke test.
- CI: add transport: [default, inprocess] matrix to the Go workflow.
- Docs and unit tests for the new connection and validation semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a96d1a82-c80e-40f1-a3df-a9708429b68b
This comment has been minimized.
This comment has been minimized.
Address three failure modes in the transport:[default,inprocess] matrix:
1. Snapshot path was cwd-relative. ConfigureForTest resolved snapshots via
filepath.Abs, which broke once the in-process path chdir'd into the temp
work dir, yielding empty snapshots ("No cached response"). Anchor the path
to the caller's source directory instead.
2. Process-global LLM inference provider / per-client telemetry. The shared
in-process runtime refuses a second provider client and ignores per-client
telemetry. Rather than silently downgrade the transport, the 8 affected
tests now skip explicitly via testharness.SkipIfInProcess (they still run
over stdio in the default cell), mirroring Rust's skip_inprocess.
3. macOS SA_ONSTACK crash. libuv installs a SIGCHLD handler without SA_ONSTACK
on first uv_spawn (during host_start); Go's runtime then aborts when it
reaps its own os/exec children. Re-arm the foreign handler with SA_ONSTACK
after host_start (sigonstack_darwin.go; no-op elsewhere), and gate the FFI
smoke test to the inprocess cell so default cells never load libnode.
Also unset ambient HMAC keys at package init when inprocess (issue #1934).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a96d1a82-c80e-40f1-a3df-a9708429b68b
This comment has been minimized.
This comment has been minimized.
Resolve the remaining inprocess-cell failures in the go-sdk-tests matrix: - Anchor all repo-relative test paths to source dirs, not the process cwd. The in-process transport os.Chdir's the whole test process into a per-test temp workdir, so cwd-relative resolution of the replay proxy (server.ts) and MCP server scripts (*.mjs) broke for every test after the first in-process one. Add testharness.RepoPath and use it for the proxy, CLI, and MCP paths. - Fix a data race in ffihost.Host: `disposed` was written under disposeMu but read under callbackMu. Unify all disposed/serverID/connectionID/activeCallbacks access under a single mutex, which also fixes a real drain-ordering bug where a late onOutbound callback could feed the receive buffer after it was closed. - Re-arm foreign signal handlers with SA_ONSTACK on linux too (not just darwin). libuv's SA_ONSTACK-less SIGCHLD handler makes the Go runtime abort when the test process reaps its own os/exec child (e.g. an MCP OAuth server) while libnode is loaded. Resolve sigaction through the runtime handle (with a libc dlopen fallback). - Make the OAuth MCP server's stderr buffer thread-safe (syncBuffer): os/exec writes to it on a goroutine while the test reads String(), a -race violation. - Skip only the readCheckpoint "unknown checkpoint" scenario in-process, where the native runtime decodes the id as u32 and rejects the large sentinel, matching Rust's explicit skip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a96d1a82-c80e-40f1-a3df-a9708429b68b
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
This comment has been minimized.
This comment has been minimized.
Exclude the native runtime payload and purego implementation from normal Go application builds while enabling them with copilot_inprocess. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Use the Linux rt_sigaction ABI for SA_ONSTACK repair, keep Go plugin test state alive through runtime shutdown, remove HMAC signing keys from Rust in-process tests, and apply the required Rust formatting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Apply the in-process build constraint to the private receive buffer so ordinary builds do not lint implementation code whose host is excluded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Do not pass --no-auto-login for the in-process harness defaults, so GH_TOKEN and GITHUB_TOKEN can authenticate against the replay proxy. Tests that require unauthenticated behavior still opt out explicitly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Adds Go in-process FFI transport, native-runtime bundling, cross-platform hosting, and CI coverage. It also aligns Python and Rust in-process APIs.
Changes:
- Implements Go FFI hosting, lifecycle management, runtime resolution, and embedding.
- Adds transport-aware Go E2E infrastructure and CI matrix coverage.
- Removes per-connection in-process paths/arguments from Python and Rust tests/APIs.
Show a summary per file
Review details
- Files reviewed: 51/52 changed files
- Comments generated: 8
- Review effort level: Medium
Forward typed runtime options, make Go startup cancellation safe, select musl runtime packages, and correct the Go replay harness working directory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Reapply SA_ONSTACK after libuv tears down its child watcher so subsequent Go subprocess cleanup cannot crash. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 447feac4-35df-4c0b-ab34-00789ad78da7
Cross-SDK Consistency Review ✅This PR does an excellent job of achieving parity across the five SDKs it touches (Go, .NET, TypeScript, Python, Rust). Here's a summary of the consistency analysis: ✅ What's aligned after this PR
|

Summary
Adds an experimental in-process (FFI) transport to the Go SDK, bringing it into semantic parity with the .NET, TypeScript, Python, and Rust SDKs. Instead of spawning a runtime child process and communicating over stdio or TCP, the SDK loads the native runtime library and pumps LSP-framed JSON-RPC over its C ABI.
The Go implementation uses
ebitengine/purego, so in-process support does not require CGO or a C toolchain.This PR also tightens the existing Python and Rust in-process APIs so all three languages expose the same transport boundary: the SDK selects and provisions the runtime, while callers use typed runtime options rather than child-process paths or arbitrary arguments.
Go changes
InProcessConnection{}— new experimental runtime connection type.-tags copilot_inprocess. Ordinary builds do not compilepurego, the FFI host, or the native runtime payload.COPILOT_CLI_PATHis supported only for an externally provisioned compatible runtime package; noPATHlookup or per-connection path/arguments are exposed.Env,WorkingDirectory, andTelemetryare rejected for in-process clients because the loaded runtime shares the host process's ambient state.Start,Stop, andForceStop.StdioConnectionandTCPConnectionnow support connection-levelEnv, with validation preventing simultaneous client- and connection-level environment blocks.COPILOT_SDK_DEFAULT_CONNECTION=inprocessandGOFLAGS=-tags=copilot_inprocess.Python consistency changes
RuntimeConnection.for_inprocess()so it accepts nopathor rawargs.pathandargsfromInProcessRuntimeConnection;COPILOT_CLI_PATHis now the only override for an externally provisioned runtime package.Rust consistency changes
ClientOptions::programandClientOptions::extra_argswithTransport::InProcess; these remain child-process-only options.COPILOT_CLI_PATHas the sole externally provisioned runtime override, matching Go and Python.--yoloargument with the equivalent permissions RPC, allowing the same tests to run on transports that do not accept process arguments.GH_TOKEN/GITHUB_TOKENauthentication.Additional cross-language parity fixes
Shared semantics
COPILOT_CLI_PATHis reserved for externally provisioned compatible runtime packages.