Skip to content

ChatGPT / Codex

Route Claude Code inference to your ChatGPT/Codex subscription by reusing ~/.codex/auth.json — auth, model slugs, effort, fast mode, and context window.

Updated View as Markdown

The codex provider routes a mapped model’s inference to your ChatGPT / Codex subscription instead of an API key. It reuses the credential the Codex CLI already wrote to ~/.codex/auth.json, so there’s nothing to paste and no per-token billing — the request is authenticated as your ChatGPT account and answered by the same backend the codex CLI talks to.

This page is the end-to-end setup. It links out to the deeper topic pages (Effort & Context, Model Discovery, Providers) rather than repeating them. To pool several ChatGPT accounts behind this provider instead of a single login, see Codex Multi-Account.

Quick start

Let a coding agent wire it up for you — shunt add prints this provider’s setup blueprint (offline and read-only; the agent edits the config, the command never does):

shunt add upstream codex --print | claude

Or follow the manual steps below.

How it works

codex is a built-in kind = "responses" provider: shunt translates Claude Code’s Anthropic Messages request into the OpenAI Responses API, sends it to the ChatGPT-account Codex backend, and translates the streamed reply back. Three things make it “Codex” rather than plain OpenAI:

Aspect Value
Endpoint <base_url>/codex/responses
Auth ChatGPT OAuth from ~/.codex/auth.json, auto-refreshed
Responses dialect Chatgpt flavor — drops params codex never sends (e.g. max_output_tokens), sends store: false, round-trips encrypted reasoning

The dialect is keyed on auth = "chatgpt_oauth", not the provider name.

Request bodies on this flavor are zstd-compressed (content-encoding: zstd), matching what the Codex CLI itself sends to the same backend. A long agentic turn re-sends its whole history every request, so this cuts the uploaded bytes substantially — most on the largest turns. It is on by default and applies only to the ChatGPT/Codex flavor; set request_compression = false on the provider to send plain JSON.

When Codex accounts are pooled, successful backend responses also populate the admin Pool health dashboard from the reported x-codex-* rate-limit windows. A roughly 5-hour window appears in 5h, a roughly 7-day window appears in 7d, and unsupported daily/monthly windows are ignored. Codex has no 7d_oi analog. The recorded windows also feed quota-aware pool selection — see Codex multi-account.

1. Log in

Log in once with the Codex CLI. shunt reads and refreshes the file it writes — it does not run its own login for Codex.

codex login

This creates ~/.codex/auth.json. If that file is missing, has no tokens, or its refresh token is gone, shunt returns an authentication_error telling you to run codex login again.

2. The provider block (optional)

codex is built in — you don’t need to declare it. This is the full default; a partial table overrides only the keys you set (config maps deep-merge):

[providers.codex]
kind = "responses"
base_url = "https://chatgpt.com/backend-api"   # shunt appends /codex/responses
auth = "chatgpt_oauth"                          # read + auto-refresh ~/.codex/auth.json
# effort = "high"                               # optional default reasoning effort (§4)
# count_tokens = "tiktoken"                      # default; "estimate" opts out

Common overrides: pin a default effort for all Codex traffic, or set count_tokens = "estimate". api_key_env / api_key_header don’t apply to chatgpt_oauth — the credential comes from the auth file. See the Configuration Reference for every key.

3. Route a model to codex

A request’s model id picks the provider. Precedence: matching [models.upstream_model] entry → exact [[routes]][[route_prefixes]]server.default_provider.

[[routes]]
model = "gpt-5.6-sol"        # the id Claude Code sends (see §4 below)
provider = "codex"
# upstream_model = "gpt-5.6-sol"   # optional: forward a different slug upstream
# effort = "high"                  # optional: pin effort for this route

upstream_model lets the id Claude Code sends differ from the slug the backend receives — the mechanism behind discovery aliases and a way to swap the real slug without touching your Claude Code env.

4. Select the model in Claude Code

Claude Code’s /model picker only honors discovery ids beginning with claude/anthropic, so a raw gpt-* id needs one of two paths — they split on the claude- prefix and don’t overlap:

claude-… discovery alias non-claude- id (gpt-5.6-sol)
/model picker via discovery ✅ auto-listed, many models ❌ dropped by Claude Code
ANTHROPIC_CUSTOM_MODEL_OPTION ❌ not honored ✅ adds to picker (one id)
CLAUDE_CODE_MAX_CONTEXT_TOKENS window ❌ ignored → 200k ✅ real window

Primary path — add the slug to the picker directly:

export ANTHROPIC_CUSTOM_MODEL_OPTION="gpt-5.6-sol"

That id is exactly what shunt routes on, so it must resolve through a matching [models.upstream_model] entry, [[routes]], or [[route_prefixes]] rule. This is the recommended path — it’s the only one that also lets you set an accurate context window. For auto-listing several Codex models in the picker instead, use a claude--named discovery alias (accepting the 200k window trade-off).

Put a subagent on a Codex slug

A subagent can run on a Codex slug while the main session stays on Claude. The model: frontmatter field accepts any string (unlike the Agent/Task tool’s model parameter, which only takes the built-in aliases). To point an existing subagent at gpt-5.6-sol, edit its .claude/agents/<name>.md and set model::

---
name: researcher
description: Deep research agent.
model: gpt-5.6-sol        # was: sonnet (or absent → inherited)
---

<the agent's system prompt — unchanged>

Spawn it without a model override (the tool parameter outranks frontmatter). Resolution order: CLAUDE_CODE_SUBAGENT_MODEL > tool model > frontmatter > inherit. To force every subagent onto one slug, set export CLAUDE_CODE_SUBAGENT_MODEL="gpt-5.6-sol".

Either way the slug needs explicit routing through a matching [models.upstream_model] entry, [[routes]], or [[route_prefixes]] rule and, being non-claude-, obeys CLAUDE_CODE_MAX_CONTEXT_TOKENS — the window follows the id automatically.

Remap the tier aliases to Codex

Instead of adding one custom id, repoint Claude Code’s built-in tier aliases at Codex slugs, so the whole session’s tier system resolves to your ChatGPT subscription (model-config env vars).

Env var Controls
ANTHROPIC_DEFAULT_HAIKU_MODEL the haiku alias and the background “small-fast” model
ANTHROPIC_DEFAULT_SONNET_MODEL the sonnet alias
ANTHROPIC_DEFAULT_OPUS_MODEL / ANTHROPIC_DEFAULT_FABLE_MODEL the opus / fable aliases

A two-tier setup — haiku → gpt-5.6-luna, sonnet → gpt-5.6-sol:

export ANTHROPIC_DEFAULT_HAIKU_MODEL="gpt-5.6-luna"
export ANTHROPIC_DEFAULT_SONNET_MODEL="gpt-5.6-sol"

# nicer picker labels (the _NAME/_DESCRIPTION companions work on a gateway)
export ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="GPT-5.6-Sol"
export ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="ChatGPT/Codex Sol via shunt"
export ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="GPT-5.6-Luna"
export ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="ChatGPT/Codex Luna via shunt (background tier)"
# shunt.toml — both resolved ids need a route
[[routes]]
model = "gpt-5.6-luna"
provider = "codex"

[[routes]]
model = "gpt-5.6-sol"
provider = "codex"

Selecting Sonnet in /model now runs gpt-5.6-sol via Codex, and every background/haiku task runs gpt-5.6-luna — the resolved id is exactly what shunt routes on, so no ANTHROPIC_CUSTOM_MODEL_OPTION is needed.

5. Reasoning effort

Set the effort with Claude Code’s usual controls (/effort, the /model slider, --effort). shunt maps it to the Responses reasoning.effort, folding max → xhigh for slugs that don’t support max (only the gpt-5.6 and gpt-6 families do).

Full precedence and the effort table: Effort & Context.

Fast mode

Codex CLI’s /fast toggle isn’t a model or effort change — it sends the Responses API top-level field service_tier: "priority", branded “Fast” (faster responses at increased usage). shunt exposes the same knob as a config key, at both provider and route level, exactly parallel to effort:

[[routes]]
model = "gpt-5.6-sol"
provider = "codex"
# service_tier = "priority"   # optional: opt in to Codex's "Fast" mode

Accepted values: fast (legacy alias, normalized to priority), priority, flex, or default (a client-only sentinel — it’s never sent on the wire). It’s off by default and sent only when explicitly configured; a route-level value wins over the provider-level default, same as effort — including a route explicitly set to default, which opts that route out of a provider-level priority/flex tier instead of inheriting it. Full key reference: Configuration.

Whatever tier ends up on the wire is also echoed to the backend in the Codex CLI’s x-codex-routing-hint header, as model=<upstream model>;tier=<tier> (just model=<upstream model> when no tier is sent). shunt sends that header only on the ChatGPT OAuth path, exactly as the Codex CLI does — on HTTP requests, and on the WebSocket transport as a handshake header. On a prefix route or via default_provider that upstream model is the client’s own model string, so the header is built defensively: it goes out only for a plausible slug (non-empty, at most 128 bytes, ASCII letters/digits and - _ . : / +), and is otherwise dropped while the request still goes out.

6. Context window

Claude Code sizes its context bar at a fixed 200k for mapped ids. gpt-5.6-sol’s real window is 372k (gpt-5.5 is 272k), so raise it for a non-claude- id:

export CLAUDE_CODE_MAX_CONTEXT_TOKENS=372000

It’s global (one value per session) and setting it larger than the real window causes prompt is too long overflow churn — match it to the smallest real window among your mapped models. shunt rewrites that overflow so Claude Code auto-compacts and retries, but each round-trip is wasted latency. Details, the live-verified boundary, and count_tokens behavior: Effort & Context.

Full example

shunt.toml:

[server]
bind = "127.0.0.1:3001"
default_provider = "anthropic"

[providers.codex]
effort = "high"     # optional: pin high effort for all Codex traffic
# service_tier = "fast"   # optional: opt in to Codex's "Fast" mode

[[routes]]
model = "gpt-5.6-sol"
provider = "codex"

Shell (both shunt and Claude Code run with these):

codex login                                          # one-time
./target/release/shunt run                           # start the gateway

export ANTHROPIC_BASE_URL=http://127.0.0.1:3001
export ANTHROPIC_CUSTOM_MODEL_OPTION="gpt-5.6-sol"   # add to /model picker
export CLAUDE_CODE_MAX_CONTEXT_TOKENS=372000         # gpt-5.6-sol's real window

Pick gpt-5.6-sol from /model. Everything else in the session still flows to Anthropic unchanged; only the mapped model’s inference is answered by your ChatGPT/Codex subscription.

Claude Code’s built-in web search works through the Codex path with no extra setup. When you enable it, Claude Code sends the hosted web_search_20250305 tool; shunt registers it as the Responses API’s hosted web_search tool, so the backend actually performs the search instead of handing it back as an unfulfilled tool call.

  • Domain filters carry over — Claude Code’s allowed_domains / blocked_domains become the Responses web_search filters.
  • Applies to the codex (ChatGPT) and openai (stock Responses) providers.
  • xAI / Grok routes don’t support it — Grok’s Responses API only accepts function tools, so shunt drops the hosted web-search tool there; use a codex or openai route for web search.

Claude Code’s tool search — deferring MCP / LSP tool schemas and revealing them on demand via a ToolSearch tool, so the model’s context isn’t spent on tools it never calls — works through the Codex path. Enable it client-side:

export ENABLE_TOOL_SEARCH=true

Claude Code disables its optimistic tool search whenever the base URL is not a first-party Anthropic host — shunt isn’t one — so without this flag every tool’s full schema is sent upstream from turn 1 and the feature is inert (it still works, it just reclaims nothing). The client’s own contract is to set ENABLE_TOOL_SEARCH=true if your proxy forwards tool_reference blocks — shunt does.

With it on, shunt maps tool search onto the OpenAI Responses API’s own native, client-executed tool_search protocol for upstreams already known to implement it, and keeps the #43 text shim everywhere else: Claude Code’s ToolSearch tool becomes a tool_search (execution: "client") tool, its tool_use becomes a tool_search_call, and the tool_reference result becomes a tool_search_output item carrying the loaded tools’ full schemas as structured JSON, appended at the tail of input. That matters because the alternative — folding a revealed tool’s schema back into the request’s tools array — invalidates the cached prompt prefix on every reveal: tools is part of that cacheable prefix, shunt re-derives it from client history each turn, and a growing array forces the backend to re-process the conversation from the tool boundary onward. The native protocol’s append-only input history keeps tools untouched, so the cached prefix survives each reveal.

  • No shunt.toml change is needed for the default (native) behavior on the built-in codex and openai providers — it’s purely a Claude Code environment variable at the ENABLE_TOOL_SEARCH level.
  • Applies to any kind = "responses" provider; whether a given one gets the native protocol or the shim by default depends on the upstream host — see Native protocol below.
  • Non-deferred tools (and the hosted web_search tool above) are always forwarded; only deferrable tools are progressively revealed.

Native protocol

Requirements — unsupported combinations silently fall back to the #43 text shim, never error. The shim still withholds each not-yet-revealed deferred tool from tools, but once Claude Code reveals one, the shim renders its full schema as tool_reference text and re-adds the tool to tools so the model can call it — sending the schema twice from then on and paying the cache-invalidation cost described above on every reveal:

  • The upstream must be a stock OpenAI or ChatGPT/Codex-flavored Responses backend. xAI/Grok routes always keep the shim.
  • The routed model must be gpt-5.4 or later (gpt-5.4, gpt-5.5, or the gpt-5.6 family). Earlier slugs (gpt-5.2 and below) keep the shim regardless of tool_search.
  • ENABLE_TOOL_SEARCH=true is still required on the Claude Code side — this flag only changes how shunt translates that feature upstream, not whether Claude Code defers tools at all.

tool_search under [providers.<name>] is a three-state setting. Left unset (the default, “auto”), it resolves to native only for a host already verified to implement tool_search items — the ChatGPT/Codex backend (the built-in codex provider) and api.openai.com (the built-in openai provider) — and to the text shim for every other OpenAI-compatible endpoint, including a custom kind = "responses" provider pointed at LiteLLM, vLLM, OpenRouter, or a self-hosted proxy: most third-party Responses backends don’t implement tool_search items, so auto-enabling native there would fail the turn instead of degrading gracefully. The native shapes were live-probe verified against the ChatGPT/Codex backend on gpt-5.6 (2026-07-13, pleaseai/shunt#86), and tool_search is documented on the public OpenAI Responses API for gpt-5.4+ models.

Set tool_search = true to opt a custom endpoint into the native path once you’ve confirmed it implements tool_search items — still subject to the flavor and model requirements above:

[providers.my-litellm]
kind = "responses"
base_url = "https://litellm.internal/v1"
tool_search = true

Set tool_search = false to force the text shim on a provider that would otherwise default to (or was opted into) the native path:

[providers.codex]
tool_search = false

Troubleshooting

Symptom Cause / Fix
ChatGPT auth not found; run codex login No ~/.codex/auth.json (or wrong $CODEX_AUTH_FILE). Run codex login.
ChatGPT auth tokens missing Auth file is in ApiKey mode — that’s the openai provider. Re-codex login with a ChatGPT account.
400 … not supported when using Codex with a ChatGPT account You used a gpt-*-codex slug. Use an entitled non--codex slug.
Model not found <slug> Client-version gating or an unentitled slug — confirm via models.json.
Effort slider ignored on a gpt-* id A route/provider effort override is winning, or no effort level is set. Verified against v2.1.224: a gpt-* id already sends output_config.effort without CLAUDE_CODE_ALWAYS_ENABLE_EFFORT, so that flag is not the fix.
Context bar over-reports / compacts early Set CLAUDE_CODE_MAX_CONTEXT_TOKENS; a discovery alias can’t take it — use a non-claude- id.
Web search returns nothing on a Grok route xAI/Grok’s Responses API doesn’t support web search; shunt drops the tool. Use a codex or openai route.
Tool search does nothing / all tool schemas sent every turn Set ENABLE_TOOL_SEARCH=true — Claude Code disables tool search by default behind a non-Anthropic base URL. shunt forwards tool_reference blocks and reveals deferred schemas on demand.
Tool search reclaims no context (still paying the shim’s cache-invalidation cost) Check the flavor/model gates — native tool_search needs a stock OpenAI/ChatGPT-Codex flavor and a gpt-5.4+ model; unsupported combinations silently fall back to the shim. If both gates pass and you’re on a custom OpenAI-compatible endpoint (LiteLLM, vLLM, OpenRouter, self-hosted), it isn’t auto-enabled — set tool_search = true once you’ve confirmed it implements tool_search items. On a known host (codex/openai), confirm you haven’t set tool_search = false; see Tool search → Native protocol above.

See the full Troubleshooting reference for more.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close