How the Codex Plugin CC Broker Endpoint and Lifecycle Work: A Complete Guide

The Codex Plugin CC manages a temporary app-server broker by generating platform-specific endpoints (Unix sockets or Windows named pipes), validating them through a readiness probe, and orchestrating the full lifecycle from process spawning to resource cleanup via ensureBrokerSession and teardownBrokerSession.

The broker in openai/codex-plugin-cc mediates communication between the companion process and the Codex runtime. Understanding how the broker endpoint is created, parsed, and managed through its lifecycle is essential for debugging connection issues or extending the plugin's functionality.

Endpoint Generation with createBrokerEndpoint

The createBrokerEndpoint function in plugins/codex/scripts/lib/broker-endpoint.mjs generates a platform-specific address string based on a temporary session directory.

Platform-Specific Creation Logic

On Windows, the function builds a sanitized pipe name using the session directory basename:

import path from "node:path";
import process from "node:process";

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: Returns a named pipe path like pipe:\\\\.\\pipe\\my-session-codex-app-server
  • macOS/Linux: Returns a Unix-domain socket path like unix:/tmp/cxc-abcd1234/broker.sock

The function receives the session directory from createBrokerSessionDir in broker-lifecycle.mjs, ensuring each broker instance gets an isolated communication channel.

Endpoint Parsing with parseBrokerEndpoint

When the companion process connects to the broker, it uses parseBrokerEndpoint (also in broker-endpoint.mjs) to validate and destructure the endpoint string:

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}`);
}

The parser strips the protocol prefix and returns a {kind, path} object that net.createConnection can consume directly. This abstraction allows the rest of the codebase to remain platform-agnostic.

Session Lifecycle Management

The high-level orchestration happens in plugins/codex/scripts/lib/broker-lifecycle.mjs through the ensureBrokerSession function, which handles the complete broker lifecycle from creation to persistence.

Session Initialization and Validation

The lifecycle begins with loadBrokerSession, which attempts to read existing session data from broker.json in the Codex state directory (resolveStateDir). If a session exists, waitForBrokerEndpoint validates it by repeatedly attempting to connect via net.createConnection until a 2-second timeout expires.

Broker Process Spawning

If no valid session exists, the lifecycle proceeds to create a fresh broker:

  1. Tear down any stale session via teardownBrokerSession
  2. Create a temporary directory with createBrokerSessionDir
  3. Generate the endpoint using the injected createBrokerEndpoint factory
  4. Spawn the broker process via spawnBrokerProcess, which executes app-server-broker.mjs
  5. Write PID and log files to the session directory for later cleanup
  6. Wait for readiness using waitForBrokerEndpoint—if the broker fails to open its socket within the timeout, the process is terminated and the function returns null

Persistence and Reuse

Once the broker is ready, saveBrokerSession persists the session data to disk. Subsequent calls to ensureBrokerSession will discover and reuse this existing broker rather than spawning a new process, significantly reducing startup latency.

Teardown and Cleanup with teardownBrokerSession

When the companion shuts down or a new session is requested, teardownBrokerSession performs deterministic cleanup:

// Simplified conceptual flow
await sendBrokerShutdown(session.endpoint); // Sends JSON-RPC shutdown message
teardownBrokerSession(session, { killProcess: process.kill });

The teardown process:

  • Terminates the broker process if a killProcess callback is provided (typically process.kill)
  • Removes PID files and log files from the session directory
  • Unlinks the Unix socket file (Windows pipes are automatically cleaned up by the OS)
  • Deletes the temporary session directory using fs.rmdirSync (best-effort)

The function tolerates missing files and malformed endpoints to ensure cleanup is robust even when the broker exits unexpectedly.

Complete Interaction Flow

A typical runtime session follows this deterministic sequence:

  1. Companion startup calls ensureBrokerSession(cwd)
  2. Discovery: If a valid broker exists, return its endpoint immediately
  3. Creation: Otherwise, create a temp directory, generate the endpoint string, and spawn app-server-broker.mjs
  4. Validation: Repeatedly connect to the endpoint until the broker signals readiness
  5. Communication: Use net.createConnection with the parsed endpoint path to send JSON-RPC messages (e.g., broker/shutdown)
  6. Shutdown: Call sendBrokerShutdown to gracefully exit the broker, then invoke teardownBrokerSession to remove all artifacts

This flow ensures no orphaned processes or stale socket files remain after the Codex runtime terminates.

Practical Usage Example

Below is a minimal implementation showing how to integrate the broker utilities into a companion process:

import { ensureBrokerSession, teardownBrokerSession, sendBrokerShutdown } from "./lib/broker-lifecycle.mjs";
import { parseBrokerEndpoint } from "./lib/broker-endpoint.mjs";
import net from "node:net";

async function startAndUseBroker(cwd) {
  // Obtain or create a broker session
  const session = await ensureBrokerSession(cwd);
  if (!session) throw new Error("Failed to start broker");

  // Parse the endpoint and connect
  const { path } = parseBrokerEndpoint(session.endpoint);
  const socket = net.createConnection({ path });
  
  // Send JSON-RPC request
  socket.write(JSON.stringify({ id: 1, method: "ping", params: {} }) + "\n");
  socket.on("data", (data) => console.log("Broker reply:", data.toString()));
  
  // Graceful shutdown sequence
  await sendBrokerShutdown(session.endpoint);
  teardownBrokerSession(session);
}

Summary

  • Endpoint generation in broker-endpoint.mjs produces platform-specific addresses using createBrokerEndpoint, returning Windows named pipes (pipe:\\...) or Unix sockets (unix:/...).
  • Parsing via parseBrokerEndpoint validates endpoint strings and returns structured {kind, path} objects for net.createConnection.
  • Lifecycle management in broker-lifecycle.mjs uses ensureBrokerSession to load existing sessions, validate endpoints with a 2-second timeout, spawn new brokers when needed, and persist session data.
  • Cleanup through teardownBrokerSession removes PID files, unlinks sockets, deletes temporary directories, and terminates processes without leaving orphaned resources.
  • Test coverage in tests/broker-endpoint.test.mjs, tests/runtime.test.mjs, and tests/process.test.mjs validates parsing logic and the complete start-to-finish lifecycle.

Frequently Asked Questions

How does the broker handle platform differences between Windows and Unix systems?

The broker uses a unified endpoint string format handled by createBrokerEndpoint and parseBrokerEndpoint in broker-endpoint.mjs. On Windows, it generates named pipes with the pipe: prefix, while on macOS and Linux it uses Unix-domain sockets with the unix: prefix. The parsing function abstracts these differences by returning a consistent {kind, path} object that the networking layer can consume regardless of platform.

What happens if the broker process fails to start or crashes?

If waitForBrokerEndpoint cannot establish a connection within the default 2-second timeout, ensureBrokerSession terminates the spawned process and returns null. The calling companion code can then handle the failure appropriately. During teardown, teardownBrokerSession is designed to tolerate missing files and malformed endpoints, ensuring that cleanup succeeds even if the broker exited unexpectedly or failed to initialize properly.

Can multiple Codex sessions reuse the same broker process?

No, each session receives its own isolated broker. The ensureBrokerSession function looks for existing sessions in the Codex state directory, but if a session is found to be invalid or if teardownBrokerSession has been called, a new temporary directory and endpoint are generated. This design ensures process isolation and prevents JSON-RPC message crosstalk between different companion instances.

Where is the broker session data stored between runs?

Session data is persisted to a broker.json file inside the Codex state directory, which is resolved via resolveStateDir in broker-lifecycle.mjs. This file contains the endpoint string and process information. When ensureBrokerSession is called subsequently, it reads this file to determine if a valid broker is already running before attempting to spawn a new one.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →