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 includingensureBrokerSession(),spawnBrokerProcess(), andteardownBrokerSession().plugins/codex/scripts/lib/broker-endpoint.mjs: Platform-aware endpoint creation and parsing logic forpipe:andunix: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: ProvidesCodexAppServerClientused by the broker to forward requests to the actual application server.plugins/codex/scripts/lib/state.mjs: SuppliesresolveStateDir()for locating the persistentbroker.jsonstorage 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 andunix:sockets on Unix systems viabroker-endpoint.mjs. - Process coordination relies on polling-based readiness detection in
waitForBrokerEndpoint()rather than complex IPC mechanisms. - Resource reuse is implemented through
broker.jsonpersistence andisBrokerEndpointReady()checks inensureBrokerSession(). - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →