Back to Omniroute

Codex CLI — Configuration with OmniRoute

docs/guides/CODEX-CLI-CONFIGURATION.md

3.8.4928.5 KB
Original Source

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

AccessURL
Local networkhttp://192.168.0.1:20128/v1
Tailscalehttp://100.x.x.x:20128/v1
Loopbackhttp://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.

DeepSeek, GLM, Kimi and others only expose a Chat Completions endpoint — not the Responses API. If you pointed Codex directly at them, it would fail.

OmniRoute solves this transparently:

Codex CLI
  → wire_api = "responses"
  → POST /v1/responses (OmniRoute)
    → OmniRoute Responses ↔ Chat Completions transformer
    → POST /chat/completions (DeepSeek / Mistral / GLM / Kimi / any provider)

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

FieldDescription
model_context_windowTotal token budget for the active model. Set to the model's advertised limit.
model_auto_compact_token_limitThreshold that triggers automatic history compaction. Maximum: 90% of model_context_window — values above 90% are silently ignored.
tool_output_token_limitCap 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_promptInline 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

ModelOmniRoute IDContext windowauto_compacttool_output_limit
GPT-5.5cx/gpt-5.5400k reliable (1M max)350,00032,768
Kimi K2.7 (thinking)kmc/kimi-k2.7131,072112,00032,768
Kimi K2.6kmc/kimi-k2.6131,072112,00032,768
GLM-5.2 / 5.2-max (thinking)glm/glm-5.2131,072112,00032,768
MiMo V2.5 Pro (thinking)opencode-go/mimo-v2.5-pro131,072112,00032,768
Qwen 3.7 Plus (thinking)opencode-go/qwen3.7-plus32,76828,00016,384
DeepSeek V4 Pro (OllamaCloud)ollamacloud/deepseek-v4-pro131,072112,00032,768
DeepSeek V4 Prods/deepseek-v4-pro1,000,000900,00065,536
MiMo V2.5opencode-go/mimo-v2.5131,072112,00032,768
Gemma 4 31B (OllamaCloud)ollamacloud/gemma4:31b32,76828,00016,384
Nemotron 3 Super (OllamaCloud)ollamacloud/nemotron-3-super32,76828,00016,384
GPT-OSS 20B (OllamaCloud)ollamacloud/gpt-oss:20b32,76828,00016,384
DeepSeek V4 Flash (OllamaCloud)ollamacloud/deepseek-v4-flash65,53656,00016,384
Gemini 3 Flash Preview (OllamaCloud)ollamacloud/gemini-3-flash-preview1,000,000850,00032,768
GLM-5 Turboglm/glm-5-turbo131,072112,00016,384
GLM-4.7 Flashglm/glm-4.7-flash131,072112,00016,384
Mistral Large Latestmistral/mistral-large-latest262,144220,00016,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 85–88% of model_context_window. Never go above 90% — silently ignored.


Model prefix: cx/

All Codex models in OmniRoute use the cx/ prefix:

Codex CLI nameOmniRoute model
cx/gpt-5.5GPT-5.5 standard
cx/gpt-5.4GPT-5.4 standard
cx/gpt-5.4-miniGPT-5.4 mini
cx/gpt-5.1-codex-miniGPT-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.

ValueUse for
noneNo reasoning — direct response
lowTrivial tasks (rename, format)
mediumServer default when not specified
highIntermediate tasks (refactoring, debug)
xhighArchitecture, 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.

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.tomlno 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

ProfileModelContextUse for
kimi-k27kmc/kimi-k2.7128kBest thinking quality (Kimi)
glm52glm/glm-5.2128kGLM thinking
glm52maxglm/glm-5.2-max128kGLM thinking max
mimo-proopencode-go/mimo-v2.5-pro128kMiMo thinking
qwen37plusopencode-go/qwen3.7-plus32kQwen thinking

Good models (bons) — high effort

ProfileModelContextUse for
kimi-k26kmc/kimi-k2.6128kGeneral purpose (Kimi)
deepseek-proollamacloud/deepseek-v4-pro128kDeepSeek Pro via OllamaCloud
deepseekds/deepseek-v4-pro1MDeepSeek Pro direct, huge context
mimoopencode-go/mimo-v2.5128kMiMo general

Simple models (simples) — no reasoning effort

ProfileModelContextUse for
gemma4ollamacloud/gemma4:31b32kCost-effective, capable
nemotronollamacloud/nemotron-3-super32kNVIDIA Nemotron
gptossollamacloud/gpt-oss:20b32kOpen-source GPT

Fast models — low effort

ProfileModelContextUse for
deepseek-flashollamacloud/deepseek-v4-flash64kQuick tasks
gemini-flashollamacloud/gemini-3-flash-preview1MVery fast, huge context
glm5turboglm/glm-5-turbo128kGLM Turbo
glm47flashglm/glm-4.7-flash128kGLM Flash
mistralmistral/mistral-large-latest256kMistral Large

Quick decision table

TaskRecommended 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.138–v0.141)

VersionFeature
v0.138Desktop app handoff (/app), v2 personal access tokens, --profile as the exclusive profile selector (legacy in-file [profiles] tables crash on startup)
v0.139web_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.141E2E 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=65536Max tokens per response
bash
# ~/.bashrc — Claude Code token cap
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536

Quick reference — CLI flags

FlagShortEffect
--model <id>-mOverrides model for this invocation
--profile <name>-pLoads ~/.codex/<name>.config.toml
--config key=value-cOverrides any config.toml field (repeatable)
--enable <feature>Force-enables a feature flag
--disable <feature>Force-disables a feature flag
--searchEnable 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:

CommandEffect
/modelOpens the model picker
/usageShows token usage for this session (v0.140)
/appHands off to the desktop app (v0.138)
/importImport a Claude Code session (v0.140)
/helpLists 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.”

SymptomLikely causeKnob
Session keeps switching accounts / prompt-cache continuity is lost between turnsSession affinity TTL is 0 (disabled)sessionAffinityTtlMs
Connection dies mid-reasoning with no client-facing promptStream idle watchdog fired after 10 minutes with no upstream chunkSTREAM_IDLE_TIMEOUT_MS

Related discussions: #7126 (long task drops), #5718 (why affinity defaults off). Tracking: #7287.

1. Session affinity — pin one conversation to one account

Default: sessionAffinityTtlMs = 0 (disabled).

Where to set it

  • Dashboard → Settings → RoutingSession affinityAffinity TTL (seconds) (ComboDefaultsTab)
  • Or PATCH settings with sessionAffinityTtlMs in milliseconds (Zod range 086_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 → 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 lengthAffinity TTL (UI, seconds)sessionAffinityTtlMs
A few hours14400 (4h)14400000
Overnight / ~12h43200 (12h)43200000
Full day86400 (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 → 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: OmniRoute’s 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 watchdog’s 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 OmniRoute’s environment:
bash
STREAM_IDLE_TIMEOUT_MS=0
FETCH_BODY_TIMEOUT_MS=0
  1. 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.
  2. Restart OmniRoute, then start the long Codex task.

Defaults decision (#7287)

KnobShip defaultChange in this guide?
sessionAffinityTtlMs0 (off)No — remains opt-in (load-balancing vs continuity; see Discussion #5718)
STREAM_IDLE_TIMEOUT_MS600000 (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 80–85% 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. 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.