# How Broker Endpoints Are Created and Socket Communication Is Managed in the Codex Plugin

> Learn how the Codex plugin creates broker endpoints and manages socket communication using platform-specific methods and JSON-RPC for efficient IPC.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-07-13

---

**The Codex plugin creates platform-specific broker endpoints (named pipes on Windows, Unix sockets on POSIX) stored in temporary session directories, then manages IPC through newline-delimited JSON-RPC messages over Node.js `net` connections with a single-active-request enforcement policy.**

The openai/codex-plugin-cc repository implements a shared broker architecture that allows multiple Codex agents to communicate with a single app-server instance via local IPC sockets. Understanding how broker endpoints are created and socket communication is managed reveals the deterministic lifecycle that enables reliable cross-platform agent coordination.

## Creating Platform-Specific Broker Endpoints

When initializing a new broker session, the `ensureBrokerSession` function in `broker-lifecycle.mjs` generates a unique session directory and delegates endpoint construction to `createBrokerEndpoint` in `broker-endpoint.mjs`.

The implementation selects the appropriate IPC mechanism based on the platform:

```javascript
// src: broker-endpoint.mjs#L10-L16
export function createBrokerEndpoint(sessionDir, platform = process.platform) {
  if (platform === "win32") {
    const pipeName = sanitizePipeName(`${path.win32.basename(sessionDir)}-codex-app-server`);
    return `pipe:\\\\.\\pipe\\${pipeName}`;
  }
  return `unix:${path.join(sessionDir, "broker.sock")}`;
}

```

**Windows** systems receive a named-pipe URI formatted as `pipe:\\.\pipe\<sanitized-name>`, while **POSIX** systems receive a Unix-domain socket URI prefixed with `unix:`. The session directory itself is created using `mkdtempSync` inside the system temporary folder (`os.tmpdir()`), ensuring unique, isolated socket paths for each broker instance.

## Parsing and Validating Endpoint Strings

All consumer code uses `parseBrokerEndpoint` to transform the URI string into a structured object consumable by Node.js networking APIs:

```javascript
// src: broker-endpoint.mjs#L19-L40
export function parseBrokerEndpoint(endpoint) {
  if (typeof endpoint !== "string" || endpoint.length === 0) {
    throw new Error("Missing broker endpoint.");
  }
  if (endpoint.startsWith("pipe:")) {
    const pipePath = endpoint.slice("pipe:".length);
    if (!pipePath) throw new Error("Broker pipe endpoint is missing its path.");
    return { kind: "pipe", path: pipePath };
  }
  if (endpoint.startsWith("unix:")) {
    const socketPath = endpoint.slice("unix:".length);
    if (!socketPath) throw new Error("Broker Unix socket endpoint is missing its path.");
    return { kind: "unix", path: socketPath };
  }
  throw new Error(`Unsupported broker endpoint: ${endpoint}`);
}

```

This function returns a `{kind, path}` object that abstracts away platform differences, allowing the rest of the codebase to use a unified connection interface.

## Establishing Socket Connections

The `connectToEndpoint` helper in `broker-lifecycle.mjs` bridges the parsed endpoint to Node.js's `net` module:

```javascript
// src: broker-lifecycle.mjs#L19-L22
function connectToEndpoint(endpoint) {
  const target = parseBrokerEndpoint(endpoint);
  return net.createConnection({ path: target.path });
}

```

This wrapper is used by all higher-level operations, including request dispatching and lifecycle management, ensuring consistent connection semantics across named pipes and Unix sockets.

## Synchronizing Broker Readiness

Before clients can transmit requests, they must verify the broker process has bound to its socket. The `waitForBrokerEndpoint` function implements an exponential polling strategy:

```javascript
// src: broker-lifecycle.mjs#L24-L40
export async function waitForBrokerEndpoint(endpoint, timeoutMs = 2000) {
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    const ready = await new Promise((resolve) => {
      const socket = connectToEndpoint(endpoint);
      socket.on("connect", () => { socket.end(); resolve(true); });
      socket.on("error", () => resolve(false));
    });
    if (ready) return true;
    await new Promise(r => setTimeout(r, 50));
  }
  return false;
}

```

This utility attempts a connection every 50 milliseconds until success or until the default 2000ms timeout expires, preventing race conditions during broker startup.

## Graceful Shutdown Protocol

To terminate the broker without dropping active connections, the client sends a JSON-RPC `broker/shutdown` message via `sendBrokerShutdown`:

```javascript
// src: broker-lifecycle.mjs#L43-L55
export async function sendBrokerShutdown(endpoint) {
  await new Promise((resolve) => {
    const socket = connectToEndpoint(endpoint);
    socket.setEncoding("utf8");
    socket.on("connect", () => {
      socket.write(`${JSON.stringify({ id: 1, method: "broker/shutdown", params: {} })}\n`);
    });
    socket.on("data", () => { socket.end(); resolve(); });
    socket.on("error", resolve);
    socket.on("close", resolve);
  });
}

```

The broker process (`app-server-broker.mjs`) listens for this method (lines 60-64) and initiates a controlled teardown, closing all client sockets and removing the session directory.

## The Broker Server Loop and Request Routing

The `app-server-broker.mjs` entry point implements the actual socket server using Node.js's `net.createServer`. After parsing the endpoint from CLI arguments, it:

1. **Creates a listener** that works for both Windows pipes and Unix sockets using the parsed endpoint path.
2. **Tracks active sockets** in a `Set` named `sockets` to ensure clean shutdown and resource cleanup.
3. **Enforces exclusivity**: Only one request socket may hold an active transaction at a time. If a second client attempts a request while the broker is busy, it receives a JSON-RPC error with code `BROKER_BUSY_RPC_CODE` (lines 73-82).
4. **Routes notifications**: The `routeNotification` function (lines 84-100) forwards server-side events to the currently active request socket.
5. **Handles special methods**: `initialize` performs the handshake, while interrupt requests are allowed to pre-empt active streams (lines 70-84).

All messages are newline-delimited JSON-RPC objects. The `send` helper at line 29 encodes messages and appends `\n`, matching the simple line-based protocol expected by `connectToEndpoint`.

## Complete Broker Lifecycle

The orchestration flows through `broker-lifecycle.mjs` in this sequence:

1. **Session initialization**: `ensureBrokerSession` creates a temporary directory, calls `createBrokerEndpoint`, spawns the broker process via `spawnBrokerProcess`, and blocks until `waitForBrokerEndpoint` confirms the socket is listening.
2. **Persistence**: The endpoint is serialized to [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json) in the session state directory (provided by `resolveStateDir` from `state.mjs`), enabling subsequent commands to reuse the same broker via `loadBrokerSession`.
3. **Request handling**: Clients open connections with `connectToEndpoint`, transmit newline-terminated JSON-RPC requests, and await responses routed through the broker to the underlying `CodexAppServerClient`.
4. **Teardown**: On process exit or explicit shutdown, `teardownBrokerSession` removes the socket file and session metadata, while the broker process cleans up its `sockets` Set and exits.

## Practical Implementation Examples

The following example demonstrates starting a broker and sending a request through the socket:

```javascript
// Start a broker and obtain its endpoint
import { ensureBrokerSession } from "./lib/broker-lifecycle.mjs";
import { parseBrokerEndpoint } from "./lib/broker-endpoint.mjs";
import net from "node:net";

async function startBroker(cwd) {
  const session = await ensureBrokerSession(cwd);
  if (!session) throw new Error("Failed to start broker");
  console.log("Broker listening at:", session.endpoint);
  return session.endpoint;
}

function sendJsonRpc(endpoint, request) {
  const { path } = parseBrokerEndpoint(endpoint);
  const socket = net.createConnection({ path });
  return new Promise((resolve, reject) => {
    socket.setEncoding("utf8");
    socket.once("data", (data) => {
      try { resolve(JSON.parse(data)); }
      catch (e) { reject(e); }
    });
    socket.once("error", reject);
    socket.write(`${JSON.stringify(request)}\n`);
  });
}

// Usage
(async () => {
  const endpoint = await startBroker(process.cwd());
  const response = await sendJsonRpc(endpoint, { 
    id: 1, 
    method: "initialize", 
    params: {} 
  });
  console.log(response);
})();

```

## Summary

- **Endpoint generation** uses `createBrokerEndpoint` in `broker-endpoint.mjs` to produce `pipe:` URIs on Windows and `unix:` URIs on POSIX, stored in unique temporary directories.
- **Connection management** relies on `parseBrokerEndpoint` and `connectToEndpoint` to abstract platform differences and create Node.js `net` connections.
- **Readiness detection** employs `waitForBrokerEndpoint` with a 50ms polling loop to ensure the broker socket is bound before clients transmit data.
- **Message protocol** consists of newline-delimited JSON-RPC messages sent over the socket, with special handling for `broker/shutdown` in `app-server-broker.mjs`.
- **Concurrency control** enforces a single-active-request policy via `BROKER_BUSY_RPC_CODE`, preventing simultaneous client requests while allowing interruptions.
- **Lifecycle cleanup** removes socket files and session metadata through `teardownBrokerSession` when the broker exits or receives a shutdown command.

## Frequently Asked Questions

### What happens if two clients try to use the broker simultaneously?

The broker enforces exclusivity through a busy-state check in `app-server-broker.mjs` (lines 73-82). If a second client attempts to send a request while another request is active, the broker immediately returns a JSON-RPC error with code `BROKER_BUSY_RPC_CODE`. Only interrupt requests are allowed to pre-empt an active stream.

### How does the broker handle platform differences between Windows and Linux/macOS?

The `createBrokerEndpoint` function detects the platform at runtime. On Windows (`win32`), it generates a named-pipe path (`pipe:\\.\pipe\<name>`), while on POSIX systems it uses a Unix-domain socket path (`unix:/tmp/.../broker.sock`). The `parseBrokerEndpoint` function normalizes these into a consistent `{kind, path}` structure, allowing `net.createConnection` to handle both mechanisms transparently.

### What is the format of messages sent over the broker socket?

All messages follow a newline-delimited JSON-RPC 2.0 protocol. Requests and responses are serialized as JSON objects terminated with a `\n` character. This line-based framing allows the broker to parse complete messages using standard readline semantics, as implemented in the `send` helper at line 29 of `app-server-broker.mjs`.

### How long does the client wait for the broker to become ready?

By default, `waitForBrokerEndpoint` waits up to **2000 milliseconds** (configurable via the `timeoutMs` parameter). It attempts a connection every 50 milliseconds until successful, returning `true` if the socket accepts connections or `false` if the timeout expires before the broker binds to its endpoint.