codex app-server
The JSON-RPC protocol behind every Codex surface — transports, the initialize handshake, thread/turn/item primitives, and the full method list.
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#
# 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:
codex app-server --listen wss://host:4500 \
--ws-auth signed-bearer-token \
--ws-shared-secret-file /path/to/secretTransports#
Three, and which one you're on changes what can break:
| Transport | Framing | Notes |
|---|---|---|
| stdio | Newline-delimited JSON | The default. What most clients use |
| websocket | One message per text frame | Experimental |
| unix socket | WebSocket over a socket file | At $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:
{
"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:
Thread a conversation between user and agent
└── Turn one exchange: user input → agent output
└── Item an individual artifact: message, command, file edit, reasoningGet this straight and the method names become self-explanatory.
Methods#
Threads#
| Method | Purpose |
|---|---|
thread/start | Create a conversation |
thread/resume | Reopen an existing thread |
thread/fork | Branch, copying history |
thread/list | Paginate stored threads |
thread/archive | Archive a thread |
thread/delete | Delete a thread |
Turns#
| Method | Purpose |
|---|---|
turn/start | Send user input — text, images, audio |
turn/steer | Add input to an already-running turn |
turn/interrupt | Cancel a running turn |
Utilities#
| Method | Purpose |
|---|---|
command/exec | Run a command inside the sandbox |
process/spawn | Run a process outside the sandbox |
fs/readFile, fs/writeFile, fs/watch | Filesystem access |
model/list | Available 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#
| Method | Purpose |
|---|---|
skills/list | Available skills — invoke with $skill-name in turn input |
experimentalFeature/list | Feature flags and their enablement state |
permissionProfile/list | Available permission profiles |
config/read, config/value/write | Read and modify user configuration |
plugin/list, plugin/install, plugin/uninstall | Marketplace plugins |
mcpServer/tool/call, mcpServerStatus/list | Configure 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:
- Server emits
item/commandExecution/requestApprovaloritem/fileChange/requestApproval - Client responds
accept,decline,cancel, oracceptForSession - Server confirms with
serverRequest/resolved - 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:
{ "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#
{ "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:
codex app-server generate-ts --out DIR
codex app-server generate-json-schema --out DIRRun 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#
RUST_LOG=debug codex app-server
LOG_FORMAT=json codex app-server # structured tracing to stderrCombine 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:
| Stage | Failure | Page |
|---|---|---|
| Before launch | Binary won't spawn | failed to start app-server |
| Before launch | Stale native-messaging manifest | manifest entry is missing |
initialize | Handshake never completes | Timed out waiting for initialize |
| After initialize | Process exits | Codex process is not available |
thread/start | Response missing an expected field | Missing 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.