microsoft/typescript-go · critical

Child process exited with code ${this.child.exitCode} before

Error message

Child process exited with code ${this.child.exitCode} before pipe was ready

What it means

On Windows, SyncRpcChannel spawns tsgo and retries openSync on the named pipe up to 500 times (10ms apart). If the child exits while the parent is still retrying, the failure is reported with the child's exit code. The pipe never appearing plus a non-zero (or early zero) exit means the server binary itself failed to start — the channel correctly surfaces the child's exit code instead of looping until timeout.

Source

Thrown at _packages/native-preview/src/api/syncChannel.ts:164

            // readSync/writeSync can't be used on stdio pipes. Instead,
            // we create a Windows named pipe path, pass it to the child
            // via --pipe, and open it with fs.openSync which returns a
            // real C-runtime fd backed by a proper HANDLE.
            const pipePath = `\\\\.\\pipe\\tsgo-sync-${process.pid}-${Date.now()}`;
            this.child = spawn(exe, [...args, "--pipe", pipePath], {
                stdio: ["ignore", "ignore", "inherit"],
            });

            // Retry openSync until the child creates the named pipe.
            let fd: number | undefined;
            for (let i = 0; i < 500; i++) {
                try {
                    fd = openSync(pipePath, "r+");
                    break;
                }
                catch {
                    if (this.child.exitCode !== null) {
                        throw new Error(
                            `Child process exited with code ${this.child.exitCode} before pipe was ready`,
                        );
                    }
                    Atomics.wait(sleepBuf, 0, 0, 10);
                }
            }
            if (fd === undefined) {
                this.child.kill();
                throw new Error("SyncRpcChannel: timed out connecting to named pipe");
            }
            this.readFd = fd;
            this.writeFd = fd;
            this.pipeFd = fd;
        }
        else {
            // POSIX: use stdio pipe file descriptors directly.
            this.child = spawn(exe, args, {
                stdio: ["pipe", "pipe", "inherit"],

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Run the configured tsserverPath binary manually with --api --cwd . to see its startup error
  2. Remove tsserverPath to use the bundled tsgo binary, which is verified to work with the client
  3. Check the exit code in the message (non-zero usually means crash; 0 often means wrong binary that exits cleanly)

Example fix

// before
new Client({ tsserverPath: "./bin/tsgo-old.exe" }); // exits at startup

// after
new Client({ cwd: projectRoot }); // use bundled binary
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync } from "node:fs";
const exeOk = (p?: string) => p === undefined || existsSync(p);

Try / catch

try { api = new API({ tsserverPath }); } catch (e) { if ((e as Error).message.includes("before pipe was ready")) { /* log exit code, verify binary by running it manually, retry with bundled binary */ } else throw e; }

Prevention

When it happens

Trigger: tsserverPath pointing at a missing/corrupt executable; tsgo crashing at startup (incompatible build, missing OS libraries, bad --api/--cwd arguments); the binary exiting immediately for any reason before creating its pipe.

Common situations: Custom tsserverPath pointing at a wrong-architecture or wrong-version binary; Windows environments missing runtime DLLs; PATH issues where the executable resolves to a non-tsgo program that exits instantly.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/6174e5d6ee1d8678. Report an issue: GitHub.