github/copilot-sdk · error

CLI child process was unexpectedly started in parent…

Error message

CLI child process was unexpectedly started in parent process mode

What it means

connectToParentProcessViaStdio is used when the library runs as a child of an editor/host and should communicate over its own stdin/stdout. In that mode a CLI child process must NOT exist; finding this.cliProcess set means the client was misconfigured or the parent-mode connect was invoked from the wrong context. The library throws to prevent corrupting the host's stdin/stdout protocol.

Solutions

  1. Pick one hosting mode: either spawn the CLI as a child (use the child-process connect path) or run in parent process mode — never both on the same instance.
  2. In parent process mode, do not call start()/spawn before connecting; construct the client and immediately use the parent-mode connect.
  3. If you previously spawned a CLI child, discard that client instance and create a fresh one for parent-process mode.
  4. Verify the hosting-mode flag/configuration passed to CopilotClient matches the connect method being invoked.

Example fix

// before
const client = new CopilotClient({ ... });
await client.start(); // spawns cliProcess
await client.connectToParentProcessViaStdio(); // throws

// after
const client = new CopilotClient({ ... });
await client.connectToParentProcessViaStdio(); // no child spawned
Defensive patterns

Strategy: validation

Validate before calling

if (hostingMode === 'parent-process') {
  // must NOT have spawned a child
  assert(!clientHasSpawnedChild, 'Do not call start() in parent-process mode');
}

Try / catch

try {
  await client.connectToParentProcessViaStdio();
} catch (err) {
  if (err instanceof Error && err.message.includes('parent process mode')) {
    client = new CopilotClient({ ...config }); // fresh instance, no spawn
    await client.connectToParentProcessViaStdio();
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the parent-process-mode connect path (e.g. connectToParentProcessViaStdio, typically via a flag indicating 'we are the plugin/child') while this.cliProcess has already been spawned by start() — i.e. mixing child-process hosting with parent-process wiring on the same client instance.

Common situations: Embedding the client inside an editor extension where the host expects to own stdio, but the code also spawned a CLI child; calling the wrong connect variant for the configured hosting mode; reusing a client configured for one mode with the other's connect call.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/da2879536c62c056. Report an issue: GitHub.

Appendix: source

Thrown at nodejs/src/client.ts:2941

        });

        // Create JSON-RPC connection over stdin/stdout
        this.messageWriter = new TeardownResilientStreamMessageWriter(this.cliProcess.stdin!);
        this.connection = createMessageConnection(
            new StreamMessageReader(this.cliProcess.stdout!),
            this.messageWriter
        );

        this.attachConnectionHandlers();
        this.connection.listen();
    }

    /**
     * Connect to parent via stdio pipes
     */
    private async connectToParentProcessViaStdio(): Promise<void> {
        if (this.cliProcess) {
            throw new Error("CLI child process was unexpectedly started in parent process mode");
        }

        // Create JSON-RPC connection over stdin/stdout
        this.messageWriter = new TeardownResilientStreamMessageWriter(process.stdout);
        this.connection = createMessageConnection(
            new StreamMessageReader(process.stdin),
            this.messageWriter
        );

        this.attachConnectionHandlers();
        this.connection.listen();
    }

    /**
     * Connect to the CLI server via TCP socket
     */
    private async connectViaTcp(): Promise<void> {
        if (!this.runtimePort) {

View on GitHub (pinned to cd8cf15dc3)