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:

  1. Clears stale state via clearBrokerSession()
  2. Creates a fresh session directory with createBrokerSessionDir()
  3. Generates the endpoint using createBrokerEndpoint() (handling cross-platform differences)
  4. Spawns the broker via spawnBrokerProcess()
  5. Waits for the endpoint to become ready with waitForBrokerEndpoint()
  6. 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:

  1. Loads the stored session via loadBrokerSession() or falls back to environment variables if the state file is missing
  2. Sends the shutdown RPC using sendBrokerShutdown() (lines 43-56 in broker-lifecycle.mjs)
  3. Cleans up running Codex jobs via cleanupSessionJobs()
  4. 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, including ensureBrokerSession() and teardownBrokerSession().

  • 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 – Implements handleSessionStart() and handleSessionEnd() 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:

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 →