Codex App-Server Protocol: How Threads Are Created and Resumed in the OpenAI Plugin

The Codex app-server protocol uses a line-delimited JSON-RPC transport over pipes or Unix sockets, with thread/start and thread/resume methods managing thread lifecycles.

The Codex app-server protocol defines how the OpenAI Codex plugin communicates with a backing server process to create and manage interactive coding sessions. This lightweight, text-based protocol enables the plugin to spawn new threads for isolated work and resume existing threads to maintain context across interactions.

JSON-RPC Transport Layer

The protocol runs over newline-separated JSON messages. Each payload is a single JSON object terminated by \n, allowing simple line-based parsing without complex framing.

Two transport implementations coexist in plugins/codex/scripts/lib/app-server.mjs:

  • SpawnedCodexAppServerClient – Direct child-process pipe to a spawned codex binary
  • BrokerCodexAppServerClient – Unix socket connection through a broker process

Both inherit common logic from AppServerClientBase at lines 57-78, which handles message serialization, response correlation, and error handling.

Message Routing

Incoming data accumulates in lineBuffer. Each newline triggers handleLine (lines 18-31), which parses the JSON and categorizes the payload:

Direction Handling logic
Server → Client request Routed to registered handlers via onRequest
Response to client request Resolved against pending promise by numeric id
Notification Dispatched to onNotification handlers

Error responses are wrapped as ProtocolError objects carrying the JSON-RPC error code and optional data field (lines 44-55).

Request Lifecycle

The request(method, params) method (lines 86-98) generates a unique numeric id, stores a pending promise, and sends the JSON line via sendMessage. The caller awaits resolution when the matching response arrives.

// Core request pattern from app-server.mjs
async request(method, params) {
  const id = this.nextId++;
  const message = { jsonrpc: "2.0", id, method, params };
  this.pending.set(id, { resolve, reject });
  this.sendMessage(JSON.stringify(message));
  // Returns promise resolved by response handler
}

Thread Creation via thread/start

New threads are created through the "thread/start" JSON-RPC method defined in app-server-protocol.d.ts (lines 59-73). This method accepts a ThreadStartParams object specifying:

  • cwd – Working directory for the session
  • model – Model identifier (nullable for default)
  • approvalPolicy – When user approval is required ("never", "prompt", etc.)
  • sandbox – Sandbox mode ("read-only", "modifiable", etc.)
  • serviceName and ephemeral flags

High-Level Wrapper: startThread

The codex.mjs module provides startThread (lines 32-47) as a convenience wrapper. It uses buildThreadParams (lines 62-71) to assemble defaults, then forwards to the underlying client:

// From plugins/codex/scripts/lib/codex.mjs
export async function startThread(client, arg, name) {
  const thread = await client.request(
    "thread/start",
    buildThreadParams(arg)  // Assembles ThreadStartParams
  );
  if (name) thread.name = name;
  return thread;
}

Practical Example: Creating a Thread

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

async function createThread(cwd) {
  const client = await CodexAppServerClient.connect(cwd);
  
  const thread = await client.request("thread/start", {
    cwd,
    model: null,                    // Use default model
    approvalPolicy: "never",        // Auto-approve low-risk actions
    sandbox: "read-only",           // Restrict filesystem access
    serviceName: "codex",
    ephemeral: true                 // Clean up on process exit
  });
  
  console.log("New thread ID:", thread.thread.id);
  await client.close();
}

Thread Resumption via thread/resume

Existing threads are resumed through the "thread/resume" method, which accepts ThreadResumeParams containing the thread ID to reactivate plus optional overrides for model, sandbox, or approval settings.

High-Level Wrapper: resumeThread

The resumeThread function (lines 50-52) is simpler than its creation counterpart—it delegates directly to buildResumeParams (lines 74-82) without additional naming logic:

// From plugins/codex/scripts/lib/codex.mjs
export function resumeThread(client, arg) {
  return client.request("thread/resume", buildResumeParams(arg));
}

buildResumeParams constructs the payload with thread ID, cwd, and any overridden settings from the input argument.

Practical Example: Resuming a Thread

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

async function continueThread(cwd, threadId) {
  const client = await CodexAppServerClient.connect(cwd);
  
  const resumed = await client.request("thread/resume", {
    threadId,                       // Required: existing thread to reactivate
    cwd,
    model: null,                    // Override or null to preserve
    approvalPolicy: "never",
    sandbox: "read-only"
  });
  
  console.log("Resumed thread:", resumed.thread.id);
  await client.close();
}

Connection Bootstrap

Static method CodexAppServerClient.connect (lines 35-53) implements the connection strategy:

  1. Detect transport – Checks CODEX_BROKER_SOCKET_PATH environment variable or explicit options to choose broker vs. direct spawn
  2. Instantiate client – Creates BrokerCodexAppServerClient or SpawnedCodexAppServerClient
  3. Initialize handshake – Sends "initialize" request and waits for server capabilities

The resulting client is ready for thread/start or thread/resume calls.

Summary

  • The Codex app-server protocol is line-delimited JSON-RPC with transport abstraction over pipes or Unix sockets
  • AppServerClientBase in app-server.mjs handles message framing, request correlation, and error wrapping
  • Thread creation uses "thread/start" with ThreadStartParams assembled by buildThreadParams in codex.mjs
  • Thread resumption uses "thread/resume" with ThreadResumeParams from buildResumeParams
  • CodexAppServerClient.connect bootstraps the connection and completes the initialization handshake

Frequently Asked Questions

What transport protocols does the Codex app-server support?

The protocol supports two transports: direct child-process pipes via SpawnedCodexAppServerClient and Unix domain sockets via BrokerCodexAppServerClient. Both use identical JSON-RPC message framing. The connect method auto-detects based on the CODEX_BROKER_SOCKET_PATH environment variable.

How are thread IDs managed across sessions?

Thread IDs are generated server-side during thread/start and returned in the response payload. To resume, the client passes this same ID to thread/resume. The server maintains thread state, allowing context preservation across disconnections and reconnections.

Can sandbox or approval settings change when resuming a thread?

Yes. The ThreadResumeParams type in app-server-protocol.d.ts declares sandbox, approvalPolicy, and model as optional fields. When provided, these override the original thread settings; when null, the server preserves existing values.

Where is the protocol specification defined?

Type definitions for all methods, parameters, and return types reside in plugins/codex/scripts/lib/app-server-protocol.d.ts. The runtime implementation and transport logic are in plugins/codex/scripts/lib/app-server.mjs, with high-level wrappers in plugins/codex/scripts/lib/codex.mjs.

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 →