# Codex Plugin Broker vs Direct Connections: How OpenAI's Codex Plugin Handles App Server Communication

> Discover how the Codex plugin handles app server communication, dynamically choosing between broker and direct connections using CodexAppServerClient.connect() for optimal performance.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: architecture
- Published: 2026-07-28

---

**The Codex plugin dynamically selects between broker and direct connections at runtime using `CodexAppServerClient.connect()`, spawning a subprocess for direct mode or connecting via Unix socket/Windows named pipe when a broker endpoint is available.**

The OpenAI Codex plugin abstracts communication with the Codex App Server through the `CodexAppServerClient` class implemented in `plugins/codex/scripts/lib/app-server.mjs`. Whether you need a lightweight direct connection for short-lived scripts or a persistent broker for session reuse, the plugin automatically determines the optimal transport strategy based on configuration options and environment variables. Understanding how the Codex plugin handles broker vs direct connections helps optimize startup performance and resource utilization across different deployment scenarios.

## Connection Architecture: Direct vs Broker Modes

The plugin implements two distinct client classes to handle different connection paradigms, both exposing the same JSON-RPC interface.

### Direct Connection Mode

**Direct mode** spawns a fresh `codex app-server` subprocess and communicates over standard I/O streams. This approach is implemented by the `SpawnedCodexAppServerClient` class.

When initializing a direct connection, the plugin executes `spawn("codex", ["app-server"], …)` (lines [89‑96](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L89-L96)). JSON-RPC messages are transmitted by writing newline-terminated JSON to the child process's **stdin** and reading responses from **stdout** (lines [68‑75](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L68-L75)).

Direct mode requires no external setup and terminates automatically when the client closes, making it ideal for CI/CD pipelines and one-off scripting tasks.

### Broker Connection Mode

**Broker mode** connects to a long-running broker process via platform-specific inter-process communication. This is implemented by the `BrokerCodexAppServerClient` class.

The broker endpoint is first parsed by `parseBrokerEndpoint` (lines [19‑40](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/broker-endpoint.mjs#L19-L40)), which handles both Unix domain sockets (Linux/macOS) and Windows named pipes. The connection is established using `net.createConnection({ path: target.path })` (lines [86‑93](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L86-L93)), with JSON-RPC exchanged over this socket identically to direct mode (lines [125‑131](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L125-L131)).

Broker mode enables multiple client sessions to share a single app server instance, significantly reducing startup overhead across repeated invocations.

## Runtime Decision Logic in CodexAppServerClient.connect

The `CodexAppServerClient.connect()` method implements a four-step resolution process to determine which connection mode to use.

### Step 1: Check for Broker Disable Flag

If the caller passes `options.disableBroker === true`, the library immediately bypasses all broker discovery logic and instantiates a direct client. This flag guarantees subprocess isolation regardless of environment configuration.

### Step 2: Locate Broker Endpoint

When broker mode is not explicitly disabled, the plugin resolves the broker endpoint through a cascading priority system:

- **Explicit option**: Uses `options.brokerEndpoint` if provided
- **Environment variable**: Falls back to `CODEX_COMPANION_APP_SERVER_ENDPOINT` (defined as `BROKER_ENDPOINT_ENV` on lines [38‑40](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L38-L40))
- **Session reuse**: If `options.reuseExistingBroker` is true, attempts to load a saved session via `loadBrokerSession` (lines [41‑42](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L41-L42))
- **New session creation**: If no endpoint exists and reuse is not requested, creates a new broker session through `ensureBrokerSession` (lines [44‑46](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L44-L46))

### Step 3: Instantiate Appropriate Client

Based on endpoint resolution:

- If an endpoint is found, instantiate `BrokerCodexAppServerClient` (lines [48‑50](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L48-L50))
- If no endpoint is resolved, instantiate `SpawnedCodexAppServerClient` (lines [48‑50](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L48-L50))

### Step 4: Initialize Communication

Both client types call `initialize()`, which performs the JSON-RPC `initialize` request followed by an `initialized` notification (lines [25‑30](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L25-L30)).

## Key Source Files and Responsibilities

| File | Purpose |
|------|---------|
| `plugins/codex/scripts/lib/app-server.mjs` | Core client implementation containing `CodexAppServerClient`, connection logic, and both `SpawnedCodexAppServerClient` and `BrokerCodexAppServerClient` classes |
| `plugins/codex/scripts/lib/broker-endpoint.mjs` | Platform-aware endpoint generation and parsing via `createBrokerEndpoint` (lines [10‑16](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/broker-endpoint.mjs#L10-L16)) and `parseBrokerEndpoint` |
| `plugins/codex/scripts/lib/broker-lifecycle.mjs` | Session persistence logic including `ensureBrokerSession`, `loadBrokerSession`, and cleanup routines |
| `plugins/codex/scripts/lib/process.mjs` | Utility for terminating process trees on Windows, used when shutting down direct mode clients |

## Practical Implementation Examples

### Establishing a Direct Connection

By default, `CodexAppServerClient.connect()` creates a direct connection when no broker configuration is present:

```javascript
import { CodexAppServerClient } from "plugins/codex/scripts/lib/app-server.mjs";

async function runDirect() {
  // No broker options → spawns direct subprocess
  const client = await CodexAppServerClient.connect(process.cwd());
  const result = await client.request("someMethod", { foo: "bar" });
  console.log(result);
  await client.close();
}

runDirect();

```

### Connecting via Explicit Broker Endpoint

Force broker mode by providing a valid endpoint string:

```javascript
import { CodexAppServerClient } from "plugins/codex/scripts/lib/app-server.mjs";

async function runViaBroker() {
  const brokerEndpoint = "unix:/tmp/my-session/broker.sock"; 
  // Windows: "pipe:\\\\.\\pipe\\my-pipe"
  
  const client = await CodexAppServerClient.connect(process.cwd(), {
    brokerEndpoint,
    disableBroker: false,
  });
  
  const info = await client.request("getInfo", {});
  console.log(info);
  await client.close();
}

runViaBroker();

```

### Reusing an Existing Broker Session

Enable session persistence across multiple plugin invocations:

```javascript
import { CodexAppServerClient } from "plugins/codex/scripts/lib/app-server.mjs";

async function runReuseBroker() {
  const client = await CodexAppServerClient.connect(process.cwd(), {
    reuseExistingBroker: true, // loads saved session if available
  });
  
  // Client communicates via existing broker socket
  const status = await client.request("getStatus", {});
  console.log(status);
  
  await client.close();
}

runReuseBroker();

```

## Summary

- The Codex plugin supports two connection modes: **direct subprocess spawning** via `SpawnedCodexAppServerClient` and **socket-based broker connections** via `BrokerCodexAppServerClient`
- Mode selection occurs at runtime inside `CodexAppServerClient.connect()` based on the `disableBroker` option, environment variables, and session reuse preferences
- Direct mode uses standard I/O streams (stdin/stdout) for JSON-RPC communication, while broker mode uses `net.createConnection()` over Unix domain sockets or Windows named pipes
- Broker endpoints are parsed by `parseBrokerEndpoint` in `broker-endpoint.mjs`, with platform-specific generation handled by `createBrokerEndpoint`
- Session persistence is managed through `broker-lifecycle.mjs`, enabling long-running brokers to serve multiple client instances

## Frequently Asked Questions

### When should I use direct mode versus broker mode?

**Use direct mode** for short-lived scripts, CI/CD pipelines, or environments where process isolation is critical and startup latency is acceptable. **Use broker mode** when running multiple sequential operations or parallel sessions, as the persistent process eliminates the overhead of repeatedly spawning the app server.

### How does the plugin handle broker endpoints on different operating systems?

The plugin automatically generates platform-appropriate endpoints via `createBrokerEndpoint` in `broker-endpoint.mjs`. On Linux and macOS, it creates Unix domain sockets (paths like `unix:/tmp/...`), while on Windows it generates named pipes (paths like `pipe:\\.\pipe\...`). The `parseBrokerEndpoint` function handles parsing both formats identically.

### Can I force the plugin to ignore existing broker environment variables?

Yes. Pass `disableBroker: true` in the connection options to force `SpawnedCodexAppServerClient` instantiation regardless of the `CODEX_COMPANION_APP_SERVER_ENDPOINT` environment variable or any saved session files. This guarantees a fresh subprocess isolated from any running broker instances.

### What happens if the broker connection fails during initialization?

If the plugin resolves a broker endpoint but fails to connect (e.g., the broker process terminated), the connection attempt will fail with a network error rather than falling back to direct mode. The current implementation does not implement automatic fallback; you must catch the error and retry with `disableBroker: true` or corrected endpoint configuration.