Lifecycle of the App Server Broker in the OpenAI Codex Plugin

The Codex plugin orchestrates an eight-phase lifecycle for its JSON-RPC broker process, managing session creation, endpoint negotiation, process spawning, readiness polling, persistence, reuse, and graceful teardown through broker-lifecycle.mjs and app-server-broker.mjs.

The openai/codex-plugin-cc repository implements a lightweight mediator architecture that connects the Codex companion to app servers via a dedicated broker process. Understanding the lifecycle of the app server broker is essential for debugging connection failures, managing resources, and extending the plugin's capabilities.

Broker Lifecycle Overview

The broker acts as a bridge between the Codex companion and the target application server, handling JSON-RPC message routing. The lifecycle ensures that only one broker runs per workspace while providing robust cleanup mechanisms to prevent orphaned processes or socket files. The implementation spans three primary files: broker-lifecycle.mjs for orchestration, broker-endpoint.mjs for transport abstraction, and app-server-broker.mjs for the runtime process.

Phase-by-Phase Breakdown

1. Session Directory Creation

When a new broker session is required, createBrokerSessionDir() generates a temporary directory (e.g., /tmp/cxc-xxxx) to house the Unix socket or named pipe, PID file, and log files. This isolation prevents collisions between concurrent workspaces. In broker-lifecycle.mjs at lines 15-17, the function creates this directory before any process spawning occurs.

2. Endpoint Generation

The createBrokerEndpoint() function in broker-endpoint.mjs (lines 10-16) constructs a platform-specific endpoint string. On Windows, it returns a pipe: prefixed path for named pipes, while on Unix-like systems, it generates a unix: prefixed socket path within the session directory. This abstraction ensures cross-platform compatibility without modifying the core logic.

3. Process Spawning

The spawnBrokerProcess() function launches app-server-broker.mjs as a detached child process. In broker-lifecycle.mjs at lines 59-66, the parent passes --endpoint, --cwd, and file paths for the PID and log files via command-line arguments. The parent immediately redirects the child's stdio to the log file to capture initialization errors and runtime diagnostics.

4. Readiness Polling

After spawning, waitForBrokerEndpoint() (lines 24-41 in broker-lifecycle.mjs) enters a retry loop attempting to connect to the generated endpoint. The function uses a default 2-second timeout per attempt, polling until the broker has successfully bound its socket or named pipe. This synchronizes the parent process with the broker's initialization without requiring complex locking mechanisms.

5. Session Persistence

Once the socket responds, session metadata—including the endpoint, pidFile, logFile, sessionDir, and pid—is serialized to ${stateDir}/broker.json (lines 62-70). This persistence layer enables quick recovery of broker connections across plugin restarts and provides the necessary state for reuse detection.

6. Existing Session Reuse

Before creating new resources, ensureBrokerSession() calls loadBrokerSession() to check for existing broker.json files. If a persisted session exists and isBrokerEndpointReady() confirms the endpoint is still responsive (lines 13-22), the plugin returns the existing broker instead of spawning a new process. This eliminates the overhead of repeated process creation for frequent operations within the same workspace.

7. Teardown and Resource Cleanup

When the plugin shuts down or encounters fatal errors, teardownBrokerSession() (lines 73-89) executes a coordinated cleanup sequence. The function optionally terminates the child process via a configurable killProcess callback, removes the PID file, deletes the log file, unlinks the Unix socket or named pipe, and finally removes the session directory. This ensures no orphaned resources remain on the filesystem.

8. Broker-Side Shutdown Handling

The broker executable itself handles graceful termination through signal handlers and JSON-RPC methods. In app-server-broker.mjs at lines 36-44, the process listens for SIGTERM and SIGINT signals. Additionally, it exposes the broker/shutdown JSON-RPC method (lines 60-64), which triggers a graceful closure of the client connection, server socket, and PID file removal before process exit.

Key Implementation Files

The lifecycle is distributed across modular components:

  • plugins/codex/scripts/lib/broker-lifecycle.mjs: Core orchestration utilities including ensureBrokerSession(), spawnBrokerProcess(), and teardownBrokerSession().
  • plugins/codex/scripts/lib/broker-endpoint.mjs: Platform-aware endpoint creation and parsing logic for pipe: and unix: transports.
  • plugins/codex/scripts/app-server-broker.mjs: The standalone broker executable that manages the JSON-RPC server, connection forwarding, and shutdown handling.
  • plugins/codex/scripts/lib/app-server.mjs: Provides CodexAppServerClient used by the broker to forward requests to the actual application server.
  • plugins/codex/scripts/lib/state.mjs: Supplies resolveStateDir() for locating the persistent broker.json storage location.

Practical Usage Examples

Obtaining and Managing a Broker Session

import { ensureBrokerSession, teardownBrokerSession } from "./lib/broker-lifecycle.mjs";

async function getBroker(cwd) {
  const session = await ensureBrokerSession(cwd, {
    // optional overrides
    platform: process.platform,
    // custom kill function – uses `process.kill` by default
    killProcess: (pid) => process.kill(pid, "SIGTERM"),
  });
  if (!session) throw new Error("Failed to start broker");
  return session;          // { endpoint, pidFile, logFile, sessionDir, pid }
}

// Later, when done:
function closeBroker(session) {
  teardownBrokerSession({
    endpoint: session.endpoint,
    pidFile: session.pidFile,
    logFile: session.logFile,
    sessionDir: session.sessionDir,
    pid: session.pid,
    killProcess: (pid) => process.kill(pid, "SIGTERM"),
  });
}

Requesting Broker Shutdown via JSON-RPC

import net from "node:net";
import { parseBrokerEndpoint } from "./lib/broker-endpoint.mjs";

async function requestShutdown(endpoint) {
  const target = parseBrokerEndpoint(endpoint);
  const socket = net.createConnection({ path: target.path });
  socket.setEncoding("utf8");
  socket.write(`${JSON.stringify({ id: 1, method: "broker/shutdown", params: {} })}\n`);
  socket.end();
}

Summary

  • The app server broker lifecycle spans eight distinct phases from session creation to graceful shutdown.
  • Session isolation is achieved through temporary directories created by createBrokerSessionDir() before process spawning.
  • Cross-platform transport uses pipe: endpoints on Windows and unix: sockets on Unix systems via broker-endpoint.mjs.
  • Process coordination relies on polling-based readiness detection in waitForBrokerEndpoint() rather than complex IPC mechanisms.
  • Resource reuse is implemented through broker.json persistence and isBrokerEndpointReady() checks in ensureBrokerSession().
  • Cleanup guarantees are provided by teardownBrokerSession(), which handles process termination, file deletion, and socket removal.

Frequently Asked Questions

How does the Codex plugin prevent multiple broker processes for the same workspace?

The plugin implements session reuse through ensureBrokerSession(), which first calls loadBrokerSession() to read existing broker.json metadata. If the saved endpoint responds to isBrokerEndpointReady(), the function returns the existing session immediately instead of spawning a new process. This singleton pattern ensures only one broker mediates connections per workspace.

What triggers the graceful shutdown of the app server broker?

The broker responds to three shutdown signals: the JSON-RPC method broker/shutdown (handled at lines 60-64 in app-server-broker.mjs), SIGTERM, and SIGINT (handled at lines 36-44). Upon receiving any of these, the broker closes its client connection, shuts down the server socket, removes its PID file, and exits cleanly. The parent process can also trigger termination via the killProcess callback passed to teardownBrokerSession().

Where does the plugin store broker session information?

Session metadata is persisted in ${stateDir}/broker.json, where stateDir is resolved by the state.mjs module. This JSON file contains the endpoint string, PID file path, log file location, session directory, and process ID. The plugin uses this file to recover broker connections after restarts and to validate reuse eligibility before spawning new processes.

How does the broker handle different operating systems?

The broker-endpoint.mjs module abstracts platform differences through createBrokerEndpoint(), which generates pipe: prefixed paths for Windows named pipes and unix: prefixed paths for Unix domain sockets. The parseBrokerEndpoint() function handles the inverse, allowing the rest of the lifecycle code to remain platform-agnostic while the transport layer adapts to the underlying operating system's IPC mechanisms.

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 →