# How the Wrapper Process Spawns and Manages the VS Code Child Process in code-server

> Discover how the code-server wrapper spawns VS Code child processes using IPC and manages their lifecycle with automatic relaunch. Learn about this dual-process architecture today.

- Repository: [Coder/code-server](https://github.com/coder/code-server)
- Tags: internals
- Published: 2026-03-01

---

**The code-server wrapper uses a dual-process architecture where a parent Node process spawns a sandboxed child process via IPC, performs a handshake to exchange CLI arguments, and manages the child's lifecycle through signal handlers and automatic relaunch capabilities.**

The `code-server` repository implements a robust process-wrapper layer that isolates VS Code's server runtime from the outer CLI interface. This design ensures that CLI argument parsing, logging, and lifecycle management occur in a stable parent process while the actual VS Code server runs in a disposable child process.

## Parent and Child Detection

The wrapper determines its role at runtime by checking for the `CODE_SERVER_PARENT_PID` environment variable in [`src/node/wrapper.ts`](https://github.com/coder/code-server/blob/main/src/node/wrapper.ts). If this variable exists, the current process instantiates as a **ChildProcess**; otherwise, it becomes a **ParentProcess**.

```typescript
// src/node/wrapper.ts
export const wrapper =
  typeof process.env.CODE_SERVER_PARENT_PID !== "undefined"
    ? new ChildProcess(parseInt(process.env.CODE_SERVER_PARENT_PID))
    : new ParentProcess(require("../../package.json").version);

```

This detection mechanism allows the same [`src/node/entry.ts`](https://github.com/coder/code-server/blob/main/src/node/entry.ts) file to serve as both the initial entry point and the child process target. The `isChild()` utility function leverages this wrapper instance to branch execution logic accordingly.

## The Entry Point Flow

In [`src/node/entry.ts`](https://github.com/coder/code-server/blob/main/src/node/entry.ts), the application bifurcates based on the wrapper's detected role. The **parent path** parses CLI arguments and initiates the spawn sequence, while the **child path** completes a handshake with the parent before starting the VS Code server.

```typescript
// src/node/entry.ts – parent path
if (!isChild(wrapper)) {
    // Parse CLI, validate arguments
    return wrapper.start(args);
}

// src/node/entry.ts – child path
if (isChild(wrapper)) {
    const args = await wrapper.handshake();
    // Prevent process.exit and run VS Code server
}

```

When operating as a child, the wrapper immediately calls `wrapper.preventExit()` to replace `process.exit` with a no-op warning function, ensuring that the VS Code server cannot accidentally terminate the process before the parent receives proper notification.

## Spawning the VS Code Child Process

The `ParentProcess.start()` method stores validated arguments and delegates to `ParentProcess._start()`, which forks a new Node process using Node.js's `child_process.fork()`. This creates an isolated runtime environment with full IPC support.

```typescript
// src/node/wrapper.ts – spawn implementation
private spawn(): cp.ChildProcess {
  return cp.fork(path.join(__dirname, "entry"), {
    env: {
      ...process.env,
      CODE_SERVER_PARENT_PID: process.pid.toString(),
      NODE_EXEC_PATH: process.execPath,
    },
    stdio: ["pipe", "pipe", "pipe", "ipc"],
  });
}

```

The spawned child inherits the parent's environment variables but receives `CODE_SERVER_PARENT_PID` set to the parent's process ID. This enables the child to instantiate the correct `ChildProcess` class and establish communication channels.

## The Handshake Mechanism

After spawning, the parent and child perform a synchronous handshake to exchange the parsed `DefaultedArgs` configuration. The child sends an initial handshake request, and the parent responds with the CLI arguments needed to start the server.

**Parent-side handshake in [`src/node/wrapper.ts`](https://github.com/coder/code-server/blob/main/src/node/wrapper.ts):**

```typescript
const message = await onMessage<ChildMessage, ChildHandshakeMessage>(
  child,
  m => m.type === "handshake",
  this.logger,
);
this.send(child, { type: "handshake", args: this.args });

```

**Child-side handshake:**

```typescript
this.send({ type: "handshake" });
const message = await onMessage<ParentMessage, ParentHandshakeMessage>(/* ... */);

```

This bidirectional message exchange ensures that the child receives the exact argument configuration parsed by the parent, eliminating the need for redundant CLI parsing in the child process.

## Lifecycle Management and Relaunch

Both process classes inherit from a base `Process` class that registers signal listeners for **SIGINT**, **SIGTERM**, and **exit** events. When these signals fire, the wrapper invokes `disposeChild()` to terminate the counterpart process gracefully before calling `wrapper.exit`.

```typescript
// Process constructor registers signal listeners
process.on("SIGINT", () => this._onDispose.emit("SIGINT"));
process.on("SIGTERM", () => this._onDispose.emit("SIGTERM"));
process.on("exit", () => this._onDispose.emit(undefined));

```

The wrapper supports **parent-driven relaunch** when the child sends a `{type:"relaunch", version}` message—typically triggered after a server update—or when the parent receives `SIGUSR1` or `SIGUSR2` signals. The current child is disposed, and `spawn()` creates a fresh process using the original arguments.

```typescript
case "relaunch":
  this.logger.info(`Relaunching: ${this.currentVersion} -> ${message.version}`);
  this.currentVersion = message.version;
  this.relaunch();

```

## Output Redirection and Logging

The parent process captures all output from the child's `stdout` and `stderr` streams, piping data to both rotating log files and the parent's own output streams. This ensures that VS Code server logs persist even if the child process crashes or exits unexpectedly.

```typescript
if (child.stdout) { 
  child.stdout.on("data", data => { 
    // Write to log file and parent stdout
  }); 
}
if (child.stderr) { 
  child.stderr.on("data", data => { 
    // Write to log file and parent stderr
  }); 
}

```

## Summary

- **Role detection** occurs via `CODE_SERVER_PARENT_PID` in [`src/node/wrapper.ts`](https://github.com/coder/code-server/blob/main/src/node/wrapper.ts), instantiating either `ParentProcess` or `ChildProcess`
- **Entry point** logic in [`src/node/entry.ts`](https://github.com/coder/code-server/blob/main/src/node/entry.ts) branches between `wrapper.start()` for parents and `wrapper.handshake()` for children
- **Spawning** uses `cp.fork()` with IPC channels to create isolated Node processes that re-execute the entry point
- **Handshake protocol** exchanges `DefaultedArgs` from parent to child through typed IPC messages
- **Lifecycle management** includes signal handling for graceful shutdown, `preventExit` guards in children, and automatic relaunch capabilities for updates
- **Log persistence** is achieved by piping child streams to rotating files in the parent process

## Frequently Asked Questions

### How does code-server detect if it's running as a parent or child process?

code-server checks the `CODE_SERVER_PARENT_PID` environment variable at startup in [`src/node/wrapper.ts`](https://github.com/coder/code-server/blob/main/src/node/wrapper.ts). If the variable is defined, the process creates a `ChildProcess` instance; otherwise, it creates a `ParentProcess`. This detection happens before [`src/node/entry.ts`](https://github.com/coder/code-server/blob/main/src/node/entry.ts) branches its execution logic.

### What is the handshake mechanism between the parent and child processes?

The handshake is a bidirectional IPC message exchange defined in [`src/node/wrapper.ts`](https://github.com/coder/code-server/blob/main/src/node/wrapper.ts). The child sends `{type:"handshake"}` immediately after spawning, and the parent responds with `{type:"handshake", args: this.args}` containing the parsed CLI configuration. This ensures the child receives validated arguments without re-parsing them.

### How does code-server handle automatic restarts after updates?

The child process can send a `{type:"relaunch", version}` message to the parent, or the parent can receive `SIGUSR1`/`SIGUSR2` signals. In either case, the parent calls `disposeChild()` to terminate the current process, then invokes `spawn()` again using the original arguments, effectively restarting the VS Code server with the new version.

### What prevents the VS Code child process from exiting prematurely?

The `ChildProcess` class calls `wrapper.preventExit()` in [`src/node/entry.ts`](https://github.com/coder/code-server/blob/main/src/node/entry.ts), which monkey-patches `process.exit` with a function that logs a warning instead of terminating. This prevents the VS Code server from accidentally killing the process before the parent can handle cleanup or logging finalization.