codex app-server

The JSON-RPC protocol behind every Codex surface — transports, the initialize handshake, thread/turn/item primitives, and the full method list.

Last reviewed · verified against the pingdotgg/t3code repo

codex app-server is the JSON-RPC interface that every Codex surface talks to. The VS Code extension, the desktop app, the mobile integration and third-party GUIs like T3 Code are all clients of it.

Understanding it is the difference between guessing at an error and knowing which stage failed.

Starting it#

bash
# stdio, the default
codex app-server

# websocket, experimental
codex app-server --listen ws://127.0.0.1:4500

# unix socket
codex app-server --listen unix://

With authentication, for remote connections:

bash
codex app-server --listen wss://host:4500 \
  --ws-auth signed-bearer-token \
  --ws-shared-secret-file /path/to/secret

Transports#

Three, and which one you're on changes what can break:

TransportFramingNotes
stdioNewline-delimited JSONThe default. What most clients use
websocketOne message per text frameExperimental
unix socketWebSocket over a socket fileAt $CODEX_HOME/app-server-control/app-server-control.sock

It is JSON-RPC 2.0, but note the deviation: messages are sent without the standard "jsonrpc": "2.0" field. A strict JSON-RPC client library will reject them.

On the websocket transport there are health probes at GET /readyz and GET /healthz. Requests carrying an Origin header are rejected with 403 — a deliberate anti-CSRF measure that surprises people trying to connect from a browser page.

The initialize handshake#

Every connection must send initialize before anything else:

json
{
  "method": "initialize",
  "id": 0,
  "params": {
    "clientInfo": {
      "name": "my_client",
      "title": "My Client",
      "version": "0.1.0"
    },
    "capabilities": {
      "experimentalApi": true,
      "optOutNotificationMethods": ["thread/started"]
    }
  }
}

The server replies with a user agent string, the codexHome path and platform metadata. The client then sends an initialized notification.

Three primitives#

The whole API is built on a hierarchy:

text
Thread          a conversation between user and agent
└── Turn        one exchange: user input → agent output
    └── Item    an individual artifact: message, command, file edit, reasoning

Get this straight and the method names become self-explanatory.

Methods#

Threads#

MethodPurpose
thread/startCreate a conversation
thread/resumeReopen an existing thread
thread/forkBranch, copying history
thread/listPaginate stored threads
thread/archiveArchive a thread
thread/deleteDelete a thread

Turns#

MethodPurpose
turn/startSend user input — text, images, audio
turn/steerAdd input to an already-running turn
turn/interruptCancel a running turn

Utilities#

MethodPurpose
command/execRun a command inside the sandbox
process/spawnRun a process outside the sandbox
fs/readFile, fs/writeFile, fs/watchFilesystem access
model/listAvailable models and reasoning efforts

The command/exec versus process/spawn split is the sandbox boundary. If you're auditing what an agent can reach, that's the line to look at.

Discovery and configuration#

MethodPurpose
skills/listAvailable skills — invoke with $skill-name in turn input
experimentalFeature/listFeature flags and their enablement state
permissionProfile/listAvailable permission profiles
config/read, config/value/writeRead and modify user configuration
plugin/list, plugin/install, plugin/uninstallMarketplace plugins
mcpServer/tool/call, mcpServerStatus/listConfigure and call MCP servers

Call experimentalFeature/list defensively. Unrecognised feature keys have crashed the app-server rather than degrading gracefully, so knowing what your build actually supports is worth the round trip.

Note that the app-server manages MCP servers — it is not itself one. Why that distinction matters →

The approval flow#

When Codex wants to do something requiring consent, it doesn't just proceed:

  1. Server emits item/commandExecution/requestApproval or item/fileChange/requestApproval
  2. Client responds accept, decline, cancel, or acceptForSession
  3. Server confirms with serverRequest/resolved
  4. Work resumes or stops

acceptForSession is what a "don't ask again" checkbox maps onto. This is also the machinery behind T3 Code's Supervised mode — supervised surfaces these requests to you, full access auto-accepts them. Same protocol, different client policy.

The event stream#

After turn/start, the client reads notifications until the turn ends:

json
{ "method": "turn/started",             "params": { "turn": { } } }
{ "method": "item/started",             "params": { "item": { } } }
{ "method": "item/agentMessage/delta",  "params": { "delta": "..." } }
{ "method": "item/completed",           "params": { "item": { } } }
{ "method": "turn/completed",           "params": { "turn": { } } }

item/agentMessage/delta is the token streaming you see in every Codex UI.

A complete exchange#

json
{ "method": "thread/start", "id": 10, "params": {
    "model": "gpt-5.1-codex",
    "cwd": "/Users/me/project"
} }
{ "id": 10, "result": { "thread": { "id": "thr_123" } } }

{ "method": "turn/start", "id": 11, "params": {
    "threadId": "thr_123",
    "input": [{ "type": "text", "text": "Run tests" }]
} }
{ "id": 11, "result": { "turn": { "id": "turn_456" } } }

Then read notifications until turn/completed.

Note the shape of the thread/start result — result.thread.id. A client that expects a field which isn't there fails exactly the way the remote sessionId error does.

Backpressure#

When saturated the server rejects with error code -32001, "Server overloaded; retry later." Clients are expected to back off exponentially with jitter. If you're building a client and hammering it, this is the code to handle rather than treat as fatal.

Generate types instead of writing them#

The single most useful thing to know if you're building a client. Rather than hand-writing types against an API that changes between releases:

bash
codex app-server generate-ts --out DIR
codex app-server generate-json-schema --out DIR

Run these against the exact Codex version you target. Generated artifacts are the only honest way to keep a client pinned to a known protocol shape — and given how much breakage here traces to version skew, that matters more than it might seem.

Debugging#

bash
RUST_LOG=debug codex app-server
LOG_FORMAT=json codex app-server    # structured tracing to stderr

Combine both when filing a bug report. A structured log makes a report actionable instead of a guess.

Mapping errors to protocol stages#

The practical payoff of knowing the protocol — each failure belongs to a specific stage:

StageFailurePage
Before launchBinary won't spawnfailed to start app-server
Before launchStale native-messaging manifestmanifest entry is missing
initializeHandshake never completesTimed out waiting for initialize
After initializeProcess exitsCodex process is not available
thread/startResponse missing an expected fieldMissing sessionId

When you can name the stage, the fix is usually obvious.

Why third-party GUIs exist#

Because this is a documented, stable-ish interface, anyone can write a Codex front end. That's how T3 Code works — it spawns codex app-server, speaks JSON-RPC over stdio, and renders the notification stream in a browser. T3 Code's architecture →

It also means the app-server is a compatibility surface between two independently-versioned programs, which is the structural reason version skew causes so many Codex problems.

FAQ#

What is codex app-server?#

It's the JSON-RPC 2.0 interface that Codex clients — the VS Code extension, the desktop app, mobile, and third-party GUIs — use to drive the Codex agent. It runs as a subprocess and communicates over stdio, websocket or a unix socket.

What protocol does codex app-server use?#

JSON-RPC 2.0, but sent without the standard "jsonrpc": "2.0" field. Over stdio it's newline-delimited JSON; the websocket transport sends one message per text frame.

How do I connect to codex app-server?#

Launch it, send an initialize request with your clientInfo and capabilities, send the initialized notification, then call thread/start followed by turn/start and read notifications until turn/completed.

What port does codex app-server use?#

Over stdio — the default — there is no port; communication is through the subprocess's standard input and output. The unix socket transport uses a socket file under $CODEX_HOME.

Why does codex app-server reject my WebSocket connection with 403?#

The websocket transport rejects requests that carry an Origin header, which is an anti-CSRF measure. Connecting from browser JavaScript triggers it, since browsers always set Origin.

What is error -32001 in codex app-server?#

Backpressure: "Server overloaded; retry later." It means the server is saturated, and clients should retry with exponential backoff and jitter rather than failing.

Should I use the app-server or the Codex SDK?#

Use the app-server for interactive clients that need per-event streaming and approval control. Use the SDK for automation and CI — it's substantially less work and covers most use cases.

How do I get accurate types for the app-server API?#

Run codex app-server generate-ts --out DIR or codex app-server generate-json-schema --out DIR against the Codex version you're targeting, rather than hand-writing them. The schema changes between releases.