Broker Lifecycle in the Codex Plugin: How Sessions Are Created, Managed, and Cleaned Up
The Codex plugin runs a local JSON-RPC broker process that lazily initializes on first use, persists session state to disk, and gracefully shuts down via JSON-RPC when the Claude Code session ends.
The openai/codex-plugin-cc repository implements a robust session management system that isolates each Claude Code conversation through a dedicated broker process. This broker acts as a thin proxy between Claude Code and the Codex App Server, managing Unix sockets or Windows pipes to ensure reliable communication. Understanding the broker lifecycle helps developers debug connection issues and extend the plugin's functionality.
The Four Phases of Broker Lifecycle
The broker lifecycle in the Codex plugin follows a strict four-phase pattern implemented in plugins/codex/scripts/lib/broker-lifecycle.mjs.
1. Create a Session Directory
Each broker session begins with resource isolation. The function createBrokerSessionDir() creates a temporary directory using mkdtempSync to hold the broker's Unix socket, PID file, and log file. This ensures concurrent Claude Code sessions never clash over shared files.
The session directory typically resides in the system temp folder with a unique prefix, providing a clean workspace for the broker's runtime artifacts.
2. Spin Up the Broker Process
Once the directory exists, spawnBrokerProcess() launches the broker binary (app-server-broker.mjs) as a detached child process using child_process.spawn with detached: true. The process writes its PID to a file in the session directory and redirects stdout/stderr to a dedicated log file.
This detachment allows the broker to outlive the initial Node.js process if needed, though it remains managed by the session lifecycle hooks.
3. Register the Session
The ensureBrokerSession() function orchestrates endpoint generation and persistence. It calls createBrokerEndpoint() to generate a platform-specific address—Unix domain sockets use unix:<tmp>/broker.sock while Windows uses named pipes—and stores this in a state file (broker.json).
Before returning the session object, the code waits for the socket to become ready via waitForBrokerEndpoint(), ensuring clients never connect to a partially initialized broker.
4. Tear-Down and Shutdown
When a Claude Code session ends, teardownBrokerSession() handles cleanup. First, it sends a graceful shutdown JSON-RPC request (broker/shutdown) to allow the broker to close active connections cleanly. Then it removes the PID file, log file, Unix socket, and finally deletes the temporary session directory.
If the broker process fails to exit gracefully, an optional killProcess callback (such as terminateProcessTree) forcibly terminates the process tree.
How Sessions Are Managed
The Codex plugin uses lazy initialization to avoid starting the broker until absolutely necessary.
Session Start Hook
The SessionStart hook in session-lifecycle-hook.mjs writes environment variables for the session ID and transcript path via handleSessionStart(), but does not start the broker immediately. This defers resource consumption until the first Codex command executes.
// From session-lifecycle-hook.mjs
export async function handleSessionStart() {
process.env.CODEX_SESSION_ID = generateSessionId();
process.env.CODEX_TRANSCRIPT_PATH = createTranscriptPath();
// Broker not started yet - deferred to first use
}
First Request Initialization
When a Codex command issues its first request, ensureBrokerSession() checks for an existing healthy broker using isBrokerEndpointReady(). If the socket is dead or missing, it:
- Clears stale state via
clearBrokerSession() - Creates a fresh session directory with
createBrokerSessionDir() - Generates the endpoint using
createBrokerEndpoint()(handling cross-platform differences) - Spawns the broker via
spawnBrokerProcess() - Waits for the endpoint to become ready with
waitForBrokerEndpoint() - Persists the session via
saveBrokerSession()for reuse
This resilience ensures that crashed or stale brokers are automatically replaced without user intervention.
Active Session Management
During the session, all RPCs flow through app-server-broker.mjs. The broker maintains a CodexAppServerClient instance and tracks active request/stream sockets to enforce single-client semantics. If a new request arrives while another is processing, the broker returns a "busy" error code.
Session End Hook
The SessionEnd hook triggers the cleanup sequence:
- Loads the stored session via
loadBrokerSession()or falls back to environment variables if the state file is missing - Sends the shutdown RPC using
sendBrokerShutdown()(lines 43-56 in broker-lifecycle.mjs) - Cleans up running Codex jobs via
cleanupSessionJobs() - Calls
teardownBrokerSession()to delete files and kill the process if needed
Practical Code Examples
Starting or Reusing a Broker Session
Use ensureBrokerSession() to lazily initialize or retrieve an existing healthy session:
import { ensureBrokerSession } from "./plugins/codex/scripts/lib/broker-lifecycle.mjs";
async function getBroker() {
const cwd = process.cwd(); // repository root
const session = await ensureBrokerSession(cwd, {
timeoutMs: 3000,
env: { ...process.env, CUSTOM_VAR: "value" }
});
if (!session) throw new Error("Failed to start broker");
// Returns: { endpoint, pidFile, logFile, sessionDir, pid }
return session;
}
This function automatically handles stale session detection and broker recreation.
Graceful Shutdown
Send a structured shutdown request to allow the broker to clean up resources:
import { sendBrokerShutdown } from "./plugins/codex/scripts/lib/broker-lifecycle.mjs";
async function stopBroker(endpoint) {
await sendBrokerShutdown(endpoint);
// Sends JSON-RPC method: broker/shutdown
}
Manual Cleanup for Testing
Force immediate teardown including process termination:
import { teardownBrokerSession } from "./plugins/codex/scripts/lib/broker-lifecycle.mjs";
import { terminateProcessTree } from "./plugins/codex/scripts/lib/process.mjs";
function forceCleanup(session) {
teardownBrokerSession({
endpoint: session.endpoint,
pidFile: session.pidFile,
logFile: session.logFile,
sessionDir: session.sessionDir,
pid: session.pid,
killProcess: terminateProcessTree // Kills entire process tree
});
}
Key Implementation Files
The broker lifecycle spans four critical files in the repository:
-
plugins/codex/scripts/lib/broker-lifecycle.mjs– Core utilities for session creation, persistence, loading, and teardown, includingensureBrokerSession()andteardownBrokerSession(). -
plugins/codex/scripts/lib/broker-endpoint.mjs– Constructs platform-specific endpoint strings (Unix sockets vs. Windows pipes) and parses them for connection handling. -
plugins/codex/scripts/session-lifecycle-hook.mjs– ImplementshandleSessionStart()andhandleSessionEnd()hooks that wire environment variables and trigger broker shutdown. -
plugins/codex/scripts/app-server-broker.mjs– The actual broker process that accepts JSON-RPC over the socket/pipe, forwards calls to the Codex App Server, and manages the single-client concurrency model.
Summary
- Resource Isolation: Each session creates a unique temporary directory via
createBrokerSessionDir()to prevent socket and PID file collisions between concurrent Claude Code instances. - Lazy Initialization: The broker starts only when
ensureBrokerSession()detects an actual request, not during session initialization. - Cross-Platform Support:
createBrokerEndpoint()abstracts Unix domain sockets and Windows named pipes behind a unified interface. - Graceful Degradation: Stale brokers are automatically detected and replaced, while shutdown uses JSON-RPC to ensure clean socket closure before file deletion.
- Process Management: The broker runs as a detached child process managed through PID files, with fallback to forced termination via
terminateProcessTree.
Frequently Asked Questions
How does the Codex plugin handle broker crashes during an active session?
If the broker crashes or becomes unresponsive, ensureBrokerSession() detects the dead socket during the next request via isBrokerEndpointReady(). It automatically clears the stale state with clearBrokerSession(), creates a fresh session directory, spawns a new broker process, and waits for the new endpoint to become ready before completing the request.
What happens to the broker if Claude Code exits unexpectedly?
When Claude Code exits normally, the SessionEnd hook sends a JSON-RPC broker/shutdown request and cleans up files. If the exit is abnormal (crash or SIGKILL), the broker process may remain running temporarily, though the next Claude Code session will detect the stale socket and replace it. The temporary session directory files persist until the next successful cleanup or system temp directory purge.
Why does the broker use Unix domain sockets instead of TCP ports?
Unix domain sockets provide faster inter-process communication than TCP loopback and avoid port conflicts. The createBrokerEndpoint() function generates paths like unix:/tmp/codex-xxx/broker.sock on Unix systems, while Windows falls back to named pipes. This design ensures the broker is accessible only to local processes and avoids firewall or networking issues.
Can multiple Claude Code sessions share the same broker process?
No, each Claude Code session maintains its own isolated broker. The createBrokerSessionDir() function generates unique temporary directories for each session, and the session state file (broker.json) is scoped to the specific Claude Code workspace. This isolation prevents cross-session contamination and allows independent shutdown of broker processes when individual sessions end.
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 →