# Troubleshooting

> Diagnosing `Not initialized`, overload retries, turn failures and `codexErrorInfo`, strict config parse errors, MCP startup `failed` status, and websocket `Origin` rejections.

- Repository: openai/codex
- GitHub: https://github.com/openai/codex
- Human docs: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1
- Complete Markdown: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1/llms-full.txt

## Source Files

- `README.md`
- `src/error_code.rs`
- `src/server_request_error.rs`
- `src/request_processors/request_errors.rs`
- `src/mcp_refresh.rs`
- `tests/suite/v2/mcp_server_status.rs`

---

---
title: "Troubleshooting"
description: "Diagnosing `Not initialized`, overload retries, turn failures and `codexErrorInfo`, strict config parse errors, MCP startup `failed` status, and websocket `Origin` rejections."
---

`codex app-server` surfaces integration failures as JSON-RPC errors on RPC responses, mid-turn `error` notifications, terminal `turn/completed` payloads, startup stderr from `--strict-config`, and HTTP/WebSocket handshake status codes on experimental websocket listeners. Use the tables below to map symptoms to the exact wire identifiers the server emits.

## Symptom map

| Symptom | Typical surface | JSON-RPC code / HTTP status | Primary fix |
| --- | --- | --- | --- |
| RPC before handshake | Response error | `-32600`, message `Not initialized` | Run `initialize`, then send `initialized` |
| Second `initialize` on same connection | Response error | `-32600`, message `Already initialized` | Open a new transport connection |
| Saturated ingress queue | Response error | `-32001`, message `Server overloaded; retry later.` | Exponential backoff with jitter |
| Oversized `turn/start` input | Response error | `-32602` + `data.input_error_code: "input_too_large"` | Trim input to `MAX_USER_INPUT_TEXT_CHARS` |
| Turn or upstream failure | `error` notification and/or `turn/completed` with `status: "failed"` | N/A (notification) | Read `error.codexErrorInfo` and `message` |
| Unknown `config.toml` keys at startup | Process exit, stderr | N/A | Fix config or drop `--strict-config` |
| Required MCP server cannot start | `thread/start` / `thread/resume` RPC error | `-32600` or internal message | Fix `[mcp_servers.*]` transport; check `required = true` |
| Optional MCP server fails | `mcpServer/startupStatus/updated` | N/A (notification) | Read `status: "failed"` and `error` |
| Browser-like websocket client blocked | WebSocket handshake | HTTP `403 Forbidden` | Omit `Origin` or use stdio/unix; do not rely on browser fetch |
| Remote websocket without auth | WebSocket handshake | HTTP `401 Unauthorized` | Pass `Authorization: Bearer …` per `--ws-auth` mode |

```text
Connection opened
    │
    ├─► initialize (RPC) ──► InitializeResponse
    │         │
    │         └─► initialized (client notification)
    │
    ├─► other RPCs ──► rejected until initialize completes ("Not initialized")
    │
    └─► turn/start ──► turn/started … turn/completed
              │              │
              │              ├─► error (optional, same payload shape as failed turn)
              │              └─► status: completed | interrupted | failed
```

## `Not initialized` and `Already initialized`

Each transport connection maintains its own session. The server rejects every client RPC except `initialize` until that connection’s `initialize` handler has committed session state.

<Steps>
<Step title="Send initialize first">
Issue one `initialize` request per connection with valid `clientInfo` (`name` must be a valid HTTP header value). Wait for the `InitializeResponse` before any other method on that connection.
</Step>
<Step title="Emit initialized">
After the response, send the client notification `initialized` (no `id`). Integration tests and embedders treat this as part of the documented handshake even though RPC gating keys off the completed `initialize` RPC.
</Step>
<Step title="Verify per-connection isolation">
On websocket transports, initialize responses are connection-scoped: a second parallel connection still returns `Not initialized` until it completes its own `initialize`. Duplicate request IDs on different connections are routed independently.
</Step>
</Steps>

| Error message | When it happens | JSON-RPC `code` |
| --- | --- | --- |
| `Not initialized` | Any RPC except `initialize` before the connection session is initialized | `-32600` (`invalid_request`) |
| `Already initialized` | Second `initialize` on the same connection | `-32600` |
| `Invalid clientInfo.name: '…'. Must be a valid HTTP header value.` | `clientInfo.name` cannot be encoded as an HTTP header | `-32600` |

<Warning>
Do not share one JSON-RPC `id` namespace across connections. Request IDs are scoped per connection; reusing IDs on a single connection returns `duplicate request id` in the in-process path and can confuse response routing on socket transports.
</Warning>

After `initialize` succeeds, the server may emit connection-scoped `config/warning` notifications (non-strict mode) and sets outbound notification delivery for that websocket connection. Thread-scoped events still require `thread/start` or `thread/resume`.

## Server overload (`-32001`)

Bounded queues sit between transport ingress, request processing, and outbound writes. When the transport event queue cannot accept another incoming **request**, the server responds on the same connection with:

```json
{
  "error": {
    "code": -32001,
    "message": "Server overloaded; retry later."
  }
}
```

<Info>
Treat `-32001` as retryable. Use exponential backoff with jitter. Avoid tight loops that replay large `turn/start` payloads while the server is saturated.
</Info>

In-process embedding uses the same numeric code with different messages when internal channels are full (`in-process app-server request queue is full`, `in-process server request queue is full`). Clients should key off `code === -32001`, not only the message string.

If the outbound queue is also full, the transport layer may drop the overload error response and log `dropping overload response`; persistent overload under extreme load can look like hung requests—reduce ingress rate and check `RUST_LOG` transport warnings.

## Turn failures and `codexErrorInfo`

Failed turns end with `turn/completed` where `turn.status` is `failed`. The nested error object matches mid-turn `error` notifications:

| Field | Type | Meaning |
| --- | --- | --- |
| `message` | string | Human-readable failure text |
| `codexErrorInfo` | `CodexErrorInfo` enum (optional) | Structured category for UI branching |
| `additionalDetails` | string (optional) | Extra provider or internal detail |

Common `codexErrorInfo` variants (wire names are camelCase):

| Variant | Typical cause |
| --- | --- |
| `contextWindowExceeded` | Context over model limit |
| `usageLimitExceeded` | Account or workspace usage cap |
| `serverOverloaded` | Upstream capacity (distinct from JSON-RPC `-32001`) |
| `httpConnectionFailed` | HTTP errors; may include `httpStatusCode` |
| `responseStreamConnectionFailed` | Cannot open Responses SSE stream |
| `responseStreamDisconnected` | SSE dropped mid-turn |
| `responseTooManyFailedAttempts` | Retry budget exhausted |
| `activeTurnNotSteerable` | `turn/start` / `turn/steer` during `/review`, manual `/compact`, etc. |
| `unauthorized` | Auth failure talking to upstream |
| `badRequest` | Invalid upstream request |
| `sandboxError` | Sandbox policy violation |
| `internalServerError` | Unclassified server fault |
| `other` | Fallback when no specific variant applies |

<Steps>
<Step title="Capture the terminal notification">
Subscribe to `turn/completed` and read `turn.error` when `turn.status === "failed"`.
</Step>
<Step title="Check for a preceding error event">
The `error` notification uses the same payload shape and may arrive before `turn/completed`. Deduplicate in UI if both are shown.
</Step>
<Step title="Branch on codexErrorInfo">
Use `httpStatusCode` when present on HTTP-related variants. For steer failures, inspect `activeTurnNotSteerable.turnKind`.
</Step>
<Step title="Inspect thread status">
After a failed turn, `thread/read` or `thread/list` may report `thread.status` as `systemError` while the thread remains loaded.
</Step>
</Steps>

Rejected `turn/start` with oversized text returns JSON-RPC `-32602` (`invalid_params`) and structured `data`:

```json
{
  "input_error_code": "input_too_large",
  "max_chars": <MAX_USER_INPUT_TEXT_CHARS>,
  "actual_chars": <count>
}
```

The limit is enforced across all text segments in `turn/start` / `turn/steer` `input` (summed character counts).

## Strict config parse errors

<ParamField body="--strict-config" type="flag">
When set, unknown fields in `config.toml` cause startup failure instead of falling back to defaults.
</ParamField>

Without `--strict-config`, a load error produces a `config/warning` notification with summary `Invalid configuration; using defaults.` and the process continues with default config.

Strict mode failure example (integration test expectation):

```text
unknown configuration field `foo`
```

Process exits non-zero; stderr contains the unknown field name. Fix or remove the typo, or run without `--strict-config` for lenient parsing (not recommended for production integrations that must fail closed on config drift).

`config/batchWrite` followed by MCP changes can queue per-thread refresh; `queue_strict_refresh` fails the whole operation if any loaded thread cannot rebuild MCP config, while `queue_best_effort_refresh` logs warnings and continues other threads.

## MCP startup `failed` status

MCP integration problems appear in two places: live startup notifications, and RPC failures when required servers are mandatory.

### Startup notifications

During `thread/start` (and resume paths that initialize MCP), the server emits:

**Method:** `mcpServer/startupStatus/updated`  
**Params:** `{ name, status, error }`

| `status` | `error` field |
| --- | --- |
| `starting` | `null` |
| `ready` | `null` |
| `failed` | non-null string (for example `MCP client for \`optional_broken\` failed to start`) |
| `cancelled` | `null` |

Optional broken servers allow `thread/start` to succeed while still emitting `failed` for the broken name. Required servers block the RPC:

```text
required MCP servers failed to initialize … required_broken …
```

In `config.toml`, mark only truly mandatory integrations with `required = true` under `[mcp_servers.<name>]`.

### Inventory RPC (`mcpServerStatus/list`)

`mcpServerStatus/list` returns tool, resource, and auth snapshots for configured servers. It does not replay startup state machines—use `mcpServer/startupStatus/updated` for `failed` transitions. Pass `threadId` when servers are defined in project-local `.codex/config.toml`; without `threadId`, project-scoped entries may be absent. Use `detail: "toolsAndAuthOnly"` to avoid slow inventory RPCs on servers with large resource catalogs.

After editing MCP config on disk, call `config/mcpServer/reload` to reload from disk and queue refresh for loaded threads (applied on each thread’s next active turn) without restarting the binary.

## Websocket `Origin` rejections and auth

Experimental websocket mode (`--listen ws://IP:PORT`) shares one TCP listener for health checks and JSON-RPC over WebSocket frames.

### Origin middleware

Any HTTP request that includes an `Origin` header is rejected with **403 Forbidden** before routing—including WebSocket upgrade attempts from browser environments. Health checks without `Origin` succeed:

| Path | Without `Origin` | With `Origin` |
| --- | --- | --- |
| `GET /healthz` | `200 OK` | `403 Forbidden` |
| `GET /readyz` | `200 OK` | `403 Forbidden` |
| WebSocket upgrade | Auth rules below | `403 Forbidden` |

<Tip>
CLI and IDE integrations should use stdio or the unix control socket (`unix://`) for local control-plane traffic. Avoid browser `fetch` / WebSocket clients that automatically attach `Origin`.
</Tip>

### Bearer auth on non-loopback binds

Binding to a non-loopback address without `--ws-auth` refuses to start. Configure one of:

| `--ws-auth` mode | CLI companions | Handshake failure |
| --- | --- | --- |
| `capability-token` | `--ws-token-file` or `--ws-token-sha256` | `401` with `missing websocket bearer token` or `invalid websocket bearer token` |
| `signed-bearer-token` | `--ws-shared-secret-file`, optional issuer/audience/skew | `401` for missing, invalid, or expired JWT |

Loopback listeners (`127.0.0.1`) may connect without a bearer token, but an `Origin` header still triggers `403` as in integration tests (`https://evil.example`).

## Logging and verification

| Variable / flag | Effect |
| --- | --- |
| `RUST_LOG` | Filter/verbosity for tracing (for example `RUST_LOG=warn` or `debug`) |
| `LOG_FORMAT=json` | One JSON object per line on stderr |
| `CODEX_HOME` | Config and state root used when reproducing strict-config failures |

<Check>
**Stdio smoke test:** spawn `codex-app-server`, send `initialize` → `initialized`, then `thread/start`. No `-32600` / `Not initialized` should appear after the handshake completes.
</Check>

## Related pages

<CardGroup>
<Card title="Connection lifecycle" href="/connection-lifecycle">
Per-connection `initialize`, `clientInfo`, and notification opt-out.
</Card>
<Card title="Protocol and transport" href="/protocol-and-transport">
JSON-RPC codes, backpressure `-32001`, and queue behavior.
</Card>
<Card title="CLI flags and error codes" href="/cli-flags-and-errors">
`--strict-config`, websocket auth flags, and tracing env vars.
</Card>
<Card title="Notifications and events" href="/notifications-and-events">
`turn/completed`, `error`, and `CodexErrorInfo` reference.
</Card>
<Card title="Skills, plugins, and MCP" href="/skills-plugins-and-mcp">
MCP listing, OAuth, reload, and configuration patterns.
</Card>
<Card title="Quickstart" href="/quickstart">
End-to-end handshake with one recovery note.
</Card>
</CardGroup>
