Files
OmniRoute/docs/guides/CODEX-CLI-CONFIGURATION.md

572 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Codex CLI — Configuration with OmniRoute"
version: 3.8.49
lastUpdated: 2026-08-01
---
# Codex CLI — Configuration with OmniRoute
Complete guide for using the Codex CLI pointed at OmniRoute as an OpenAI-compatible backend.
---
## Ready-to-paste config.toml
Replace `<YOUR_HOST>` and `<YOUR_KEY>` with your values:
```toml
# ~/.codex/config.toml
model = "cx/gpt-5.5"
model_provider = "omniroute"
model_reasoning_effort = "xhigh"
model_context_window = 400000
model_auto_compact_token_limit = 350000
tool_output_token_limit = 32768 # history storage cap per tool call
[model_providers.omniroute]
name = "OmniRoute"
base_url = "http://<YOUR_HOST>:20128/v1"
env_key = "OMNIROUTE_API_KEY"
requires_openai_auth = false
wire_api = "responses"
```
```bash
# ~/.bashrc or ~/.zshrc — actual key value, never in config.toml
export OMNIROUTE_API_KEY="<YOUR_KEY>"
```
### macOS: Codex bundled inside the ChatGPT app
If you installed Codex through the ChatGPT desktop app, the `codex` binary may
exist only inside the app bundle and not yet be on your shell `PATH`. Add the
resources directory to your shell startup file:
```bash
export PATH="/Applications/ChatGPT.app/Contents/Resources:$PATH"
```
Open a new shell, then verify:
```bash
command -v codex
codex --version
```
### Local unauthenticated OmniRoute: placeholder key is enough
Codex validates that the environment variable named by `env_key` exists
**before** the first request leaves the CLI. If your **local** OmniRoute
instance does not require auth, any non-empty placeholder works:
```bash
export OMNIROUTE_API_KEY="${OMNIROUTE_API_KEY:-local}"
```
Use a real key instead when your OmniRoute server is protected or remote.
> **Common host options**
>
> | Access | URL |
> | ------------- | ----------------------------- |
> | Local network | `http://192.168.0.1:20128/v1` |
> | Tailscale | `http://100.x.x.x:20128/v1` |
> | Loopback | `http://localhost:20128/v1` |
---
## `wire_api = "responses"` — why it works for all models
Codex CLI deprecated `wire_api = "chat"` (Chat Completions) in February 2026 and now **requires** `wire_api = "responses"` (OpenAI Responses API). Setting `wire_api = "chat"` causes an immediate startup crash since v0.138.
Many providers, including GLM and Kimi, still expose only a Chat Completions endpoint. DeepSeek V4 now exposes a native Responses API as well as an Anthropic-compatible endpoint; OmniRoute uses Responses by default and lets each DeepSeek connection select Anthropic compatibility.
**OmniRoute solves this transparently:**
```
Codex CLI
→ wire_api = "responses"
→ POST /v1/responses (OmniRoute)
→ OmniRoute selects the provider's native protocol and translates when needed
→ POST /responses (DeepSeek V4) or /chat/completions (Mistral / GLM / Kimi / others)
```
You never need a separate translation proxy when using OmniRoute. **All models use `wire_api = "responses"`** — OmniRoute handles the rest.
> **`wire_api` is the default** — the field defaults to `"responses"` and can be omitted entirely from `config.toml`. Only ever set it explicitly if you're documenting intent.
---
## Context window and compaction
### Token configuration fields
| Field | Description |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model_context_window` | Total token budget for the active model. Set to the model's advertised limit. |
| `model_auto_compact_token_limit` | Threshold that triggers automatic history compaction. **Maximum: 90% of `model_context_window`** — values above 90% are silently ignored. |
| `tool_output_token_limit` | Cap on tokens stored per tool call output in history. Prevents a single large tool response from filling the window. **This is not the max output** — it is a history storage cap. |
| `compact_prompt` | Inline override for the system prompt used during compaction (v0.138+). |
> **Note on `model_max_output_tokens`**: This field is **not part of the Codex CLI config schema** (absent from the Codex Rust codebase). It is silently ignored if set. Do not rely on it — use `tool_output_token_limit` to control how much tool output is stored in history.
### Context windows by model
| Model | OmniRoute ID | Context window | `auto_compact` | `tool_output_limit` |
| ------------------------------------ | ------------------------------------ | ---------------------- | -------------- | ------------------- |
| GPT-5.5 | `cx/gpt-5.5` | 400k reliable (1M max) | 350,000 | 32,768 |
| Kimi K2.7 (thinking) | `kmc/kimi-k2.7` | 131,072 | 112,000 | 32,768 |
| Kimi K2.6 | `kmc/kimi-k2.6` | 131,072 | 112,000 | 32,768 |
| GLM-5.2 / 5.2-max (thinking) | `glm/glm-5.2` | 131,072 | 112,000 | 32,768 |
| MiMo V2.5 Pro (thinking) | `opencode-go/mimo-v2.5-pro` | 131,072 | 112,000 | 32,768 |
| Qwen 3.7 Plus (thinking) | `opencode-go/qwen3.7-plus` | 32,768 | 28,000 | 16,384 |
| DeepSeek V4 Pro (OllamaCloud) | `ollamacloud/deepseek-v4-pro` | 131,072 | 112,000 | 32,768 |
| DeepSeek V4 Pro | `ds/deepseek-v4-pro` | 1,000,000 | 900,000 | 65,536 |
| MiMo V2.5 | `opencode-go/mimo-v2.5` | 131,072 | 112,000 | 32,768 |
| Gemma 4 31B (OllamaCloud) | `ollamacloud/gemma4:31b` | 32,768 | 28,000 | 16,384 |
| Nemotron 3 Super (OllamaCloud) | `ollamacloud/nemotron-3-super` | 32,768 | 28,000 | 16,384 |
| GPT-OSS 20B (OllamaCloud) | `ollamacloud/gpt-oss:20b` | 32,768 | 28,000 | 16,384 |
| DeepSeek V4 Flash (OllamaCloud) | `ollamacloud/deepseek-v4-flash` | 65,536 | 56,000 | 16,384 |
| Gemini 3 Flash Preview (OllamaCloud) | `ollamacloud/gemini-3-flash-preview` | 1,000,000 | 850,000 | 32,768 |
| GLM-5 Turbo | `glm/glm-5-turbo` | 131,072 | 112,000 | 16,384 |
| GLM-4.7 Flash | `glm/glm-4.7-flash` | 131,072 | 112,000 | 16,384 |
| Mistral Large Latest | `mistral/mistral-large-latest` | 262,144 | 220,000 | 16,384 |
> **Compaction formula:** `effective_window = model_context_window - min(tool_output_token_limit, 20000)`. Values above 20k do not change the compaction trigger.
> **Rule of thumb:** set `model_auto_compact_token_limit` to 8588% of `model_context_window`. Never go above 90% — silently ignored.
---
## Model prefix: `cx/`
All Codex models in OmniRoute use the `cx/` prefix:
| Codex CLI name | OmniRoute model |
| ----------------------- | ------------------ |
| `cx/gpt-5.5` | GPT-5.5 standard |
| `cx/gpt-5.4` | GPT-5.4 standard |
| `cx/gpt-5.4-mini` | GPT-5.4 mini |
| `cx/gpt-5.1-codex-mini` | GPT-5.1 Codex mini |
Other providers use their own prefix (`kmc/`, `glm/`, `ds/`, `ollamacloud/`, `opencode-go/`, `mistral/`) — the prefix matches the OmniRoute provider alias.
---
## Reasoning Effort
Controls how much the model "thinks" before responding.
| Value | Use for |
| -------- | --------------------------------------------- |
| `none` | No reasoning — direct response |
| `low` | Trivial tasks (rename, format) |
| `medium` | **Server default** when not specified |
| `high` | Intermediate tasks (refactoring, debug) |
| `xhigh` | Architecture, deep analysis, complex problems |
```bash
# Per invocation override
codex -c model_reasoning_effort=low "rename variable x to count"
codex -c model_reasoning_effort=xhigh "design the auth module"
```
Also set a reasoning **summary** so Desktop can render thinking text (not only encrypted blobs):
```toml
# ~/.codex/config.toml
model_reasoning_effort = "xhigh" # or ultra when supported
model_reasoning_summary = "detailed" # auto | concise | detailed | none
```
### OmniRoute Thinking Budget (server setting)
On the OmniRoute host, **Settings → AI → Thinking Budget** must be **`passthrough`** for Codex effort/summary to reach upstream. Mode **`auto` strips** all client `reasoning` / `reasoning_effort` fields and will empty thinking panels even when Codex is configured correctly.
Full guide: [THINKING_BUDGET.md](./THINKING_BUDGET.md).
Compression and prompt cache are independent and keep working under `passthrough`.
---
## Profiles — named configurations per model/workflow
Profiles let you switch model + context window with a single flag. Each profile is a flat
`~/.codex/<name>.config.toml` that overlays on top of the base `config.toml`.
> **Naming rule (Codex CLI v0.137+):** file must be `~/.codex/<name>.config.toml` — **no `profile-` prefix**.
> The CLI resolves `-p kimi-k27` → `~/.codex/kimi-k27.config.toml`. If the file is not found, the default applies silently.
```bash
codex --profile kimi-k27 "analyze 10k lines of this codebase"
codex -p glm52 "architecture review"
codex --profile deepseek-flash "rename variable" # fast, cheap
```
### Effort profiles (same model, different effort)
```bash
codex -p low # cx/gpt-5.5, effort=low
codex -p medium # cx/gpt-5.5, effort=medium
codex -p high # cx/gpt-5.5, effort=high
codex -p xhigh # cx/gpt-5.5, effort=xhigh (default)
codex -p chat # cx/gpt-5.5, no effort set (server default)
```
### Thinking models (alto pensamento) — xhigh + detailed summary
| Profile | Model | Context | Use for |
| ------------ | --------------------------- | ------- | ---------------------------- |
| `kimi-k27` | `kmc/kimi-k2.7` | 128k | Best thinking quality (Kimi) |
| `glm52` | `glm/glm-5.2` | 128k | GLM thinking |
| `glm52max` | `glm/glm-5.2-max` | 128k | GLM thinking max |
| `mimo-pro` | `opencode-go/mimo-v2.5-pro` | 128k | MiMo thinking |
| `qwen37plus` | `opencode-go/qwen3.7-plus` | 32k | Qwen thinking |
### Good models (bons) — high effort
| Profile | Model | Context | Use for |
| -------------- | ----------------------------- | ------- | --------------------------------- |
| `kimi-k26` | `kmc/kimi-k2.6` | 128k | General purpose (Kimi) |
| `deepseek-pro` | `ollamacloud/deepseek-v4-pro` | 128k | DeepSeek Pro via OllamaCloud |
| `deepseek` | `ds/deepseek-v4-pro` | 1M | DeepSeek Pro direct, huge context |
| `mimo` | `opencode-go/mimo-v2.5` | 128k | MiMo general |
### Simple models (simples) — no reasoning effort
| Profile | Model | Context | Use for |
| ---------- | ------------------------------ | ------- | ----------------------- |
| `gemma4` | `ollamacloud/gemma4:31b` | 32k | Cost-effective, capable |
| `nemotron` | `ollamacloud/nemotron-3-super` | 32k | NVIDIA Nemotron |
| `gptoss` | `ollamacloud/gpt-oss:20b` | 32k | Open-source GPT |
### Fast models — low effort
| Profile | Model | Context | Use for |
| ---------------- | ------------------------------------ | ------- | ----------------------- |
| `deepseek-flash` | `ollamacloud/deepseek-v4-flash` | 64k | Quick tasks |
| `gemini-flash` | `ollamacloud/gemini-3-flash-preview` | 1M | Very fast, huge context |
| `glm5turbo` | `glm/glm-5-turbo` | 128k | GLM Turbo |
| `glm47flash` | `glm/glm-4.7-flash` | 128k | GLM Flash |
| `mistral` | `mistral/mistral-large-latest` | 256k | Mistral Large |
### Quick decision table
| Task | Recommended profile |
| -------------------------------- | ------------------------------------------------ |
| Rename, format, boilerplate | `--profile deepseek-flash` or `-p low` |
| Explain, light review | `-p chat` or `-p gemini-flash` |
| Debug, moderate refactor | `-p medium` or `-p kimi-k26` |
| New feature, complex tests | `-p high` or `-p mimo` |
| Architecture, deep analysis | `-p kimi-k27` or `-p glm52` or `-p xhigh` |
| Codebase analysis (needs 1M ctx) | `--profile deepseek` or `--profile gemini-flash` |
| Maximum thinking quality | `-p glm52max` or `-p mimo-pro` |
| Cost-conscious | `-p gemma4` or `-p gptoss` |
---
## Generating profiles automatically with `omniroute setup-codex`
If you run OmniRoute on a VPS, you can auto-generate profile files from the live model catalog:
```bash
# From a VPS (uses local OmniRoute on port 20128)
omniroute setup-codex
# From any machine — point at your VPS
omniroute setup-codex --remote http://100.x.x.x:20128 --api-key sk-xxx
# Preview without writing files
omniroute setup-codex --remote http://100.x.x.x:20128 --dry-run
# Only generate GLM and Kimi profiles
omniroute setup-codex --only glm,kimi
# Write to a custom directory
omniroute setup-codex --codex-home /path/to/.codex
```
The command fetches `/v1/models`, uses tuned profiles for known models, falls back to catalog metadata for other compatible text models, and writes `~/.codex/<name>.config.toml` for each. Idempotent — safe to re-run.
OmniRoute can also **auto-sync** these same profile files after a successful provider model discovery/import changes the live catalog. This is **opt-in and off by default**: toggle it from the **CLI Code dashboard** ("CLI profile auto-sync" → Codex), or set `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES=true` (it also honors `CLI_ALLOW_CONFIG_WRITES`, on by default). When enabled it only writes separate `~/.codex/*.config.toml` profile files; it never changes the active/default `~/.codex/config.toml`, Codex-lb settings, auth, or provider selection.
---
## Launching Codex with `omniroute launch-codex`
Health-checks your OmniRoute instance before launching Codex:
```bash
# Launch against local OmniRoute (default port 20128)
omniroute launch-codex
# Launch with a specific profile
omniroute launch-codex --profile kimi-k27
# Launch against a remote VPS
omniroute launch-codex --remote http://100.x.x.x:20128/v1 --api-key sk-xxx
# Pass extra args to codex
omniroute launch-codex --profile glm52 -- --yolo "fix this bug"
```
---
## New Codex CLI features (v0.138v0.141)
| Version | Feature |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| v0.138 | Desktop app handoff (`/app`), v2 personal access tokens, `--profile` as the exclusive profile selector (legacy in-file `[profiles]` tables crash on startup) |
| v0.139 | `web_search = "live"` — native web search from code mode; `oneOf`/`allOf` in MCP tool schemas; `codex doctor` env diagnostics |
| v0.140 | `/usage` token view in-session; `/import` from Claude Code sessions; `codex delete <SESSION_ID>` subcommand; Amazon Bedrock auth via `aws` object in provider config |
| v0.141 | E2E encrypted Noise relay for remote executors; SQLite WAL fix; P-521 TLS support |
### New `config.toml` fields (post-v0.137)
```toml
# Native web search (v0.139)
web_search = "live" # "disabled" | "cached" | "live"
# Separate developer system prompt (v0.138)
developer_instructions = "Always prefer functional style."
# Custom compaction prompt
compact_prompt = "Summarise the above as bullet points."
# Route /review to a cheaper model
review_model = "glm/glm-5-turbo"
# OpenAI service tier
service_tier = "fast" # "fast" | "flex"
```
### New `[model_providers.<id>]` fields
```toml
[model_providers.omniroute]
base_url = "http://100.x.x.x:20128/v1"
env_key = "OMNIROUTE_API_KEY"
requires_openai_auth = false
# Static extra headers on every request
[model_providers.omniroute.http_headers]
"X-Custom-Header" = "value"
# Headers read from env vars
[model_providers.omniroute.env_http_headers]
"X-Trace-Id" = "TRACE_ID"
# Extra URL query params (useful for Azure api-version)
[model_providers.omniroute.query_params]
"api-version" = "2024-12-01-preview"
```
### Amazon Bedrock auth (v0.140)
```toml
[model_providers.bedrock]
base_url = "https://bedrock-runtime.us-east-1.amazonaws.com"
[model_providers.bedrock.aws]
profile = "default" # ~/.aws/credentials profile
region = "us-east-1"
```
---
## Multiple servers
```toml
[model_providers.omniroute-main]
base_url = "http://192.168.0.1:20128/v1"
env_key = "OMNIROUTE_API_KEY"
[model_providers.omniroute-tailscale]
base_url = "http://100.x.x.x:20128/v1"
env_key = "OMNIROUTE_API_KEY"
```
---
## Claude Code — equivalent configuration
| Codex CLI (`config.toml`) | Claude Code (env var) | Effect |
| --------------------------------- | ------------------------------------- | ----------------------- |
| `tool_output_token_limit = 32768` | _(not directly exposed)_ | Per-tool history cap |
| `model_context_window = 400000` | _(determined by the model)_ | Context window |
| — | `CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536` | Max tokens per response |
```bash
# ~/.bashrc — Claude Code token cap
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536
```
---
## Quick reference — CLI flags
| Flag | Short | Effect |
| --------------------- | ----- | -------------------------------------------- |
| `--model <id>` | `-m` | Overrides `model` for this invocation |
| `--profile <name>` | `-p` | Loads `~/.codex/<name>.config.toml` |
| `--config key=value` | `-c` | Overrides any config.toml field (repeatable) |
| `--enable <feature>` | — | Force-enables a feature flag |
| `--disable <feature>` | — | Force-disables a feature flag |
| `--search` | — | Enable live web search for this invocation |
New in v0.140:
```bash
codex delete <SESSION_ID> # delete a session
codex delete <SESSION_ID> --force # skip confirmation
codex debug models --bundled # list bundled model catalog as JSON
```
Inside an interactive session:
| Command | Effect |
| --------- | ------------------------------------------- |
| `/model` | Opens the model picker |
| `/usage` | Shows token usage for this session (v0.140) |
| `/app` | Hands off to the desktop app (v0.138) |
| `/import` | Import a Claude Code session (v0.140) |
| `/help` | Lists all slash commands |
---
## Long-running tasks
Two OmniRoute defaults can silently sabotage multi-hour Codex CLI sessions. Neither is a Codex CLI setting — both live on the OmniRoute side. Users migrating a config from upstream proxies that pin accounts and disable idle cutoffs often hit both and conclude OmniRoute “cannot sustain a long session.”
| Symptom | Likely cause | Knob |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------ |
| Session keeps switching accounts / prompt-cache continuity is lost between turns | Session affinity TTL is `0` (disabled) | `sessionAffinityTtlMs` |
| Connection dies mid-reasoning with no client-facing prompt | Stream idle watchdog fired after 10 minutes with no upstream chunk | `STREAM_IDLE_TIMEOUT_MS` |
Related discussions: [#7126](https://github.com/diegosouzapw/OmniRoute/discussions/7126) (long task drops), [#5718](https://github.com/diegosouzapw/OmniRoute/discussions/5718) (why affinity defaults off). Tracking: [#7287](https://github.com/diegosouzapw/OmniRoute/issues/7287).
### 1. Session affinity — pin one conversation to one account
**Default:** `sessionAffinityTtlMs = 0` (disabled).
**Where to set it**
- Dashboard → **Settings → Routing****Session affinity****Affinity TTL (seconds)** (`ComboDefaultsTab`)
- Or PATCH settings with `sessionAffinityTtlMs` in **milliseconds** (Zod range `0``86_400_000`, i.e. up to 24 hours)
> Renamed in #7274 from the Codex-only `codexSessionAffinityTtlMs`. The legacy key is still accepted as a read-only alias; new configs should use `sessionAffinityTtlMs`. Affinity now applies to **any** provider once the TTL is above `0`, not only Codex — see [`docs/architecture/RESILIENCE_GUIDE.md`](../architecture/RESILIENCE_GUIDE.md) → Session affinity.
**What breaks when it stays at 0**
Every turn of a multi-turn Codex conversation is routed independently by the active combo strategy and can land on a **different account per turn**. That breaks upstream session / prompt-cache continuity. OmniRoute only consults Codex session headers (`x-codex-session-id` / `x-session-id` / `x-omniroute-session`) and body fields such as `prompt_cache_key` / `session_id` when the TTL is greater than `0` (`extractSessionAffinityKey` in `src/sse/services/auth.ts`).
**Recommended for a multi-hour single task**
Set the TTL **above the expected wall-clock length of the task** (UI max is **86400 seconds** = 24 hours):
| Expected task length | Affinity TTL (UI, seconds) | `sessionAffinityTtlMs` |
| -------------------- | -------------------------- | ---------------------- |
| A few hours | `14400` (4h) | `14400000` |
| Overnight / ~12h | `43200` (12h) | `43200000` |
| Full day | `86400` (24h, maximum) | `86400000` |
Opt-in is deliberate: disabling affinity favors load-balancing across accounts; enabling it favors continuity for one long agent session. This guide does **not** change the default — operators running long Codex tasks must opt in.
### 2. Stream idle timeout — do not kill quiet reasoning turns
**Default:** `STREAM_IDLE_TIMEOUT_MS = 600000` (10 minutes). It inherits from `REQUEST_TIMEOUT_MS` when unset; the shared baseline is also 600000. See [`docs/guides/SETUP_GUIDE.md`](SETUP_GUIDE.md) → Timeouts.
**What breaks at the default**
A Codex reasoning / tool turn that stays silent for more than 10 minutes with **no real upstream chunk** is force-closed by the SSE idle watchdog (`open-sse/utils/stream.ts`). The client often sees a bare connection drop — matching “stopped automatically without any notification.”
Critical detail: OmniRoutes synthetic SSE **heartbeat does not reset** the idle clock. Only a real upstream body chunk updates `lastChunkTime`. A quiet model that is still “thinking” looks identical to a stalled upstream from the watchdogs point of view.
Related Undici body inactivity: `FETCH_BODY_TIMEOUT_MS` (also defaults to the same 10-minute baseline; `0` disables it). For streaming, `FETCH_TIMEOUT_MS` only covers connection setup / first headers — once the stream is active, stalls are governed by `STREAM_IDLE_TIMEOUT_MS` and `FETCH_BODY_TIMEOUT_MS`.
**Recommended for a multi-hour single task**
In the OmniRoute process environment (`.env` / compose / systemd):
```bash
# Disable stream idle + body inactivity cutoffs for long reasoning turns
STREAM_IDLE_TIMEOUT_MS=0
FETCH_BODY_TIMEOUT_MS=0
```
Or raise them above the longest quiet gap you expect (values are milliseconds):
```bash
# Example: allow up to 2 hours of silence between upstream chunks
STREAM_IDLE_TIMEOUT_MS=7200000
FETCH_BODY_TIMEOUT_MS=7200000
```
Restart OmniRoute after changing these env vars.
### Concrete recipe — multi-hour Codex task
1. **Pin the account:** Dashboard → Settings → Routing → Session affinity → Affinity TTL = `43200` (12h) or `86400` (24h max).
2. **Raise / disable idle cutoffs** in OmniRoutes environment:
```bash
STREAM_IDLE_TIMEOUT_MS=0
FETCH_BODY_TIMEOUT_MS=0
```
3. Keep the usual Codex `config.toml` (`wire_api = "responses"`, correct `base_url`, `OMNIROUTE_API_KEY`) — no Codex-side affinity/idle knobs exist for these two behaviors.
4. Restart OmniRoute, then start the long Codex task.
### Defaults decision (#7287)
| Knob | Ship default | Change in this guide? |
| ------------------------ | ----------------- | ----------------------------------------------------------------------------------------- |
| `sessionAffinityTtlMs` | `0` (off) | **No** — remains opt-in (load-balancing vs continuity; see Discussion #5718) |
| `STREAM_IDLE_TIMEOUT_MS` | `600000` (10 min) | **No** — remains 10 minutes for general traffic; long Codex operators raise or disable it |
Flipping either default globally would change behavior for every client of an instance, not only Codex. Document the knobs; leave the defaults alone until an explicit operator decision says otherwise.
### Diagnosing idle cuts
When the idle watchdog fires, OmniRoute logs a line shaped like:
```text
[STREAM] Idle timeout: no data from codex for 600000ms (model: cx/gpt-5.5)
```
Grep for `Idle timeout: no data from` (or the code `stream_idle_timeout` / error name `StreamIdleTimeoutError`). The provider segment is whatever OmniRoute used for that request (`codex`, another provider id, or `provider` if unknown) — it is not always the literal string `codex`.
---
## Troubleshooting
**`Error: wire_api = "chat" is no longer supported`**
Remove `wire_api = "chat"` from your config. Set `wire_api = "responses"` or omit the field (defaults to `"responses"` since v0.138).
**`Error: model not found`**
Verify the model exists in OmniRoute with the correct prefix. Use `omniroute models list` or open `/dashboard/providers/<provider>`.
**`Authentication error`**
Confirm `OMNIROUTE_API_KEY` is exported: `echo $OMNIROUTE_API_KEY`.
**`ERROR: Missing environment variable: OMNIROUTE_API_KEY`**
Codex validates that the env var exists before making the first request. Export
a real key for protected servers, or a non-empty placeholder such as
`OMNIROUTE_API_KEY=local` when your **local** OmniRoute instance does not
require auth. Restart the shell if you added it to `~/.bashrc` or `~/.zshrc`.
**`Connection refused`**
Verify OmniRoute is running and the `base_url` host/port is correct for your network (local vs Tailscale vs VPS).
**Session crashes near context limit**
Set `model_context_window` and `model_auto_compact_token_limit` explicitly. See the context window table above.
**Compaction fires too late**
Lower `model_auto_compact_token_limit` to 8085% of the window. Never set above 90%.
**Profile not loading (`-p <name>` silently ignored)**
Confirm the file exists at `~/.codex/<name>.config.toml` (no `profile-` prefix). Run `ls ~/.codex/*.config.toml`.
**Long Codex task drops mid-run / switches accounts between turns**
See [Long-running tasks](#long-running-tasks). Enable session affinity (TTL above task length) and raise or disable `STREAM_IDLE_TIMEOUT_MS` / `FETCH_BODY_TIMEOUT_MS`. Grep OmniRoute logs for `Idle timeout: no data from`.