How Claude Code Broker Communicates with Codex: JSON-RPC Architecture Explained

Claude Code communicates with Codex through a dedicated broker process that multiplexes JSON-RPC messages over platform-specific sockets, enforcing single-client semantics while allowing interrupt requests to pass through.

The openai/codex-plugin-cc repository implements a sophisticated inter-process communication layer that allows Claude Code to manage Codex CLI sessions reliably. This architecture uses a broker pattern to handle concurrency, lifecycle management, and transport abstraction across Unix sockets and Windows named pipes.

Broker Endpoint Creation

Communication begins with establishing a persistent transport channel between Claude Code and the Codex runtime.

Platform-Specific Endpoints

When a Claude Code session initiates, the plugin constructs a platform-specific endpoint using createBrokerEndpoint() in plugins/codex/scripts/lib/broker-endpoint.mjs. On macOS and Linux, this creates a Unix socket; on Windows, it generates a named pipe. The endpoint path is typically derived from the session directory to ensure isolation between concurrent projects.

import { createBrokerEndpoint } from './lib/broker-endpoint.mjs';

const endpoint = createBrokerEndpoint(sessionDir);
// Returns: unix:/tmp/session/broker.sock (Unix) or named pipe path (Windows)

The broker endpoint serves as the persistent address where the broker process listens for incoming JSON-RPC connections from Claude Code clients.

The Broker Process

The core multiplexing logic resides in plugins/codex/scripts/app-server-broker.mjs, which implements the broker process that mediates all communication between Claude Code and the Codex App-Server.

Starting the Broker

The broker is launched as a separate Node.js process that binds to the previously created endpoint and manages the lifecycle of the underlying Codex App-Server:

node plugins/codex/scripts/app-server-broker.mjs serve \
  --endpoint unix:/tmp/session/broker.sock \
  --pid-file /tmp/session/broker.pid

This process maintains a single active connection to the Codex App-Server (spawned via codex app-server) while exposing a socket interface to Claude Code clients.

Request Routing and Protocol Handling

The broker implements a line-delimited JSON-RPC protocol. Each message is newline-separated, allowing for simple parsing via standard stream interfaces. The broker handles three distinct message types:

  • Initialization requests: Returns a static initialize response identifying the broker as codex-companion-broker
  • Method requests: Forwards calls to the underlying Codex App-Server and streams responses back
  • Notifications: Routes asynchronous events from Codex back to the active client socket
// Simplified broker request handling
if (message.id !== undefined && message.method === 'initialize') {
  send(socket, { 
    id: message.id, 
    result: { userAgent: 'codex-companion-broker' } 
  });
}

Single-Client Enforcement and Interrupts

The broker enforces strict single-client semantics to prevent request interleaving. If a second client attempts to send a request while another is active, the broker returns BROKER_BUSY_RPC_CODE (-32001):

if (activeRequestSocket && activeRequestSocket !== socket) {
  send(socket, { 
    id: message.id, 
    error: buildJsonRpcError(
      BROKER_BUSY_RPC_CODE, 
      'Shared Codex broker is busy.'
    ) 
  });
}

However, the architecture permits turn/interrupt requests to bypass this restriction, allowing Claude Code to cancel in-flight operations even when the broker is ostensibly busy with a streaming response.

Client-Side Integration

Claude Code connects to the broker through the CodexAppServerClient class defined in plugins/codex/scripts/lib/app-server.mjs.

Connection Establishment

The client checks for the CODEX_COMPANION_APP_SERVER_ENDPOINT environment variable or accepts an endpoint parameter from the broker lifecycle manager. When a broker endpoint is present, the client instantiates a BrokerCodexAppServerClient that connects to the Unix socket or named pipe:

import { CodexAppServerClient } from './lib/app-server.mjs';

const client = await CodexAppServerClient.connect(cwd, {
  env: process.env,
  reuseExistingBroker: true,
});

// Send requests to Codex through the broker
const result = await client.request('review/start', { /* payload */ });

If no broker endpoint is available, the client falls back to spawning a fresh Codex App-Server process directly, maintaining compatibility with standalone usage.

Lifecycle Management

The plugins/codex/scripts/lib/broker-lifecycle.mjs module handles broker persistence across Claude Code sessions. It manages the creation of PID files, socket cleanup on shutdown, and process termination using plugins/codex/scripts/lib/process.mjs for cross-platform process tree management.

Practical Usage Example

To integrate Claude Code with Codex through the broker architecture:

  1. Start the broker with a specific endpoint and PID file:
node plugins/codex/scripts/app-server-broker.mjs serve \
  --endpoint unix:/tmp/codex/broker.sock \
  --pid-file /tmp/codex/broker.pid
  1. Connect from your application using the client library:
const client = await CodexAppServerClient.connect(process.cwd(), {
  env: { 
    ...process.env, 
    CODEX_COMPANION_APP_SERVER_ENDPOINT: 'unix:/tmp/codex/broker.sock' 
  },
  reuseExistingBroker: true,
});

// Execute Codex commands
const review = await client.request('review/start', { path: './src' });
  1. Gracefully shut down the broker when finished:
await client.request('broker/shutdown', {});
process.exit(0);

Summary

  • Transport layer: The broker uses platform-specific sockets (Unix sockets on macOS/Linux, named pipes on Windows) created via createBrokerEndpoint() in plugins/codex/scripts/lib/broker-endpoint.mjs.
  • Protocol: All communication uses newline-delimited JSON-RPC, with the broker parsing messages in plugins/codex/scripts/app-server-broker.mjs.
  • Concurrency model: Single-client semantics prevent request interleaving, returning error code -32001 for busy states, while allowing turn/interrupt requests to cancel active operations.
  • Client integration: CodexAppServerClient.connect() in plugins/codex/scripts/lib/app-server.mjs automatically detects broker availability via environment variables and handles connection fallback.
  • Lifecycle: The broker maintains PID files and performs cleanup through broker-lifecycle.mjs and process.mjs.

Frequently Asked Questions

What protocol does Claude Code use to communicate with the Codex broker?

Claude Code communicates with the Codex broker using JSON-RPC 2.0 over line-delimited streams. Each request and response is a complete JSON object terminated by a newline character, transmitted through either Unix sockets (macOS/Linux) or named pipes (Windows). This protocol allows for bidirectional communication, including method calls from Claude Code to Codex and asynchronous notifications from Codex back to Claude Code.

Why does the broker enforce single-client semantics?

The broker enforces single-client semantics to prevent interleaved JSON-RPC requests from corrupting the state of the underlying Codex App-Server. When a second client attempts to send a request while another is active, the broker returns error code -32001 (BROKER_BUSY_RPC_CODE). However, the architecture specifically exempts turn/interrupt requests from this restriction, allowing Claude Code to cancel in-flight operations through a separate socket connection even while a streaming response is in progress.

How does Claude Code find an existing broker process?

Claude Code locates existing brokers through the CODEX_COMPANION_APP_SERVER_ENDPOINT environment variable or through the broker lifecycle management logic in plugins/codex/scripts/lib/broker-lifecycle.mjs. When CodexAppServerClient.connect() is called with reuseExistingBroker: true, it checks for existing socket files and PID files to determine if a broker is already running for the current session, avoiding unnecessary process duplication.

What happens if the broker process crashes during a Codex operation?

If the broker process crashes or is terminated unexpectedly, the underlying Codex App-Server process may continue running but becomes unreachable. The CodexAppServerClient detects connection failures when attempting to read from or write to the socket. On the next operation, if the broker endpoint is no longer responsive, the client can fall back to spawning a new Codex App-Server directly, though any in-flight operations from the crashed session will be lost and must be retried.

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 →