model-gateway
Two local processes give Claude Code native access to the user's ChatGPT subscription models:
claude-code-proxy (does OpenAI OAuth and translates Anthropic Messages API to the Codex
backend) and a shim router this plugin owns. ANTHROPIC_BASE_URL points at the shim: requests
for claude-gpt-* models are un-prefixed and go to the proxy, everything else passes through
to api.anthropic.com with the user's normal claude.ai login. The shim's /v1/models advertises
Codex models with a claude- prefix because Claude Code's model discovery drops ids that don't
start with claude/anthropic. That prefix is shared with the real Anthropic ids, so the route
is decided by the backend family segment (claude-gpt-*, claude-grok-*), never by the prefix.
All commands: node ~/.claude/model-gateway/model-gateway.js <command>. SessionStart recreates this stable launcher from Claude Code's installed-plugin registry, so it follows upgrades and falls back to an installed downgrade; it reports a missing install directly.
First-time setup
Project-local wiring is the standard setup. env --write-project writes the current project's .claude/settings.local.json, so the gateway stays configured for this project's sessions and executor worktrees without putting a machine-local endpoint in a committed file. setup uses the same project-local target by default.
env --write-user remains an opt-in shared fallback for people who deliberately want one gateway URL in ~/.claude/settings.json across every project. Claude Code gives a current project's settings.local.json higher precedence, so doctor marks the winner [effective], names both files, and says that project-local wiring wins when their gateway modes disagree.
The first-run order matters:
- Install Model Gateway at the recommended project scope.
- Reload plugins so the new skill is available.
- Invoke this skill and run
setup.
- If setup says sign-in is needed, have the user complete
login, then run setup again. The second setup finishes the download, wiring, and project confirmation.
- After the project wiring is confirmed, tell the user to fully restart the Claude Code process for this same project before selecting a model.
A plugin reload alone does not reload settings or the picker cache. Do not tell the user to select a new row until that full restart is complete.
The SessionStart hook injects a one-line nudge while the gateway is in any half-configured
state; act on it. The user sees that same line in the transcript, because a state only they can fix used to
reach the model alone. Anything routine stays out of it, and the hook always exits 0 so the line survives:
run ensure yourself when you need an exit code. SessionStart waits at most 12 seconds for a newly started
gateway, then leaves its supervisor to finish in the background so it stays inside Claude Code's hook budget. setup is one-shot and idempotent: it downloads the claude-code-proxy binary
(sha256-verified) and starts everything. Re-running it later is also the upgrade path. Worker hot-replacement accepts the current CLI or a non-older version-shaped sibling under the same lexical and resolved parent directory. This checks filesystem layout, not marketplace registration; a development layout with the same structure can also satisfy it.
Setup fails before extraction when the required checksum asset is absent, its download fails, its contents are invalid or name a different archive, or the archive digest does not match. Report the verification failure; never skip the checksum check or run the unverified download.
Use the bundled lifecycle commands for restarts and drains. They authenticate with the per-user ~/.claude/model-gateway/control-token file. Never print or copy this secret into a conversation, request body, or settings file. Browser-origin lifecycle requests are refused, and a replacement worker must be this CLI or a non-older sibling within the same resolved plugin cache. Development checkouts cannot hot-switch to unrelated installations. After updating, restart the calling Claude Code process to load the current control protocol. These controls do not authenticate inference requests to the separate proxy or protect against a process with the same OS-user access.
node ~/.claude/model-gateway/model-gateway.js setup
# only if setup says sign-in is needed:
node ~/.claude/model-gateway/model-gateway.js login # browser OAuth; --device for headless
node ~/.claude/model-gateway/model-gateway.js setup # finishes the wiring
login opens the user's browser; they complete it themselves (suggest ! node ... login if it
needs a real TTY). env --write-project writes this project's .claude/settings.local.json.
Use env --write-user only when the user wants a shared fallback across projects. If both are
present, project-local wiring wins: doctor marks it [effective], names the shadowed user file,
and fails when their gateway modes differ. All wiring changes apply to new Claude Code sessions,
so restart after the write. The Codex rows appear in /model labeled "From gateway".
The winning ANTHROPIC_BASE_URL source follows Claude Code's precedence: process environment,
current-project .claude/settings.local.json, project .claude/settings.json, then user
~/.claude/settings.json. A process export always wins, so settings writes cannot replace it.
When doctor or SessionStart says that process env shadows a wired settings file, Model Gateway is
bypassed. If the user controls the Claude Code CLI launch, they can correct or unset
ANTHROPIC_BASE_URL, then restart. If the host replaces that value, use the supported Claude Code CLI
on the wired project instead. Model Gateway does not support Desktop routing under forced overrides on
Windows or macOS, and settings, parent, or User-scope edits cannot be promised to win.
env --write-user --reconcile is confirmation-gated. Plain env --write-user writes the shared
user fallback, then lists recorded projects whose local URL differs without changing their files.
The confirmed command removes only Model Gateway-owned keys from those other projects'
.claude/settings.local.json files: its base URL, the three Claude alias pins, and static gateway
flags whose values equal plugin defaults. It leaves unrelated settings alone, skips projects already
agreeing, cannot change process.env, and needs a restart to affect a new session.
Discovery needs Claude Code v2.1.129+; models shows exactly what the shim advertises. Claude Code
only refetches gateway discovery when it has an API-key credential. OAuth subscriptions do not give it
one, so Model Gateway writes Claude Code's discovery cache whenever its advertised list changes.
Restart remains necessary to surface new rows in /model: Claude Code reads the picker cache once at
session start. /reload-plugins does not reload it. Restoring or refreshing auth on an already-wired
install needs no restart of the current Claude Code process: the proxy is a separate process, so once login + setup re-authenticate it,
the next request routes through cleanly. Settings, discovery-cache, plugin, or model-row changes do need a full restart of the affected project process. Keep these two recovery paths separate. The shim supervisor also probes the proxy's /v1/models endpoint
while it runs, confirming a failed probe through a fresh connection before restarting an unavailable proxy with single-flight bounded backoff. It leaves a healthy proxy
alone. Recovery output remains in ~/.claude/model-gateway/logs/guardian.log; bounded lifecycle records in
~/.claude/model-gateway/logs/lifecycle.jsonl identify supervisor, worker, and proxy PIDs, orderly
stop/restart requests, observed exits, and recovery outcomes. Use doctor to print the evidence path and
the last observed exit. An OS termination or force-killed supervisor may leave no final record, so treat an
absent exit record as absence of evidence, not a clean shutdown. Cleanup accepts a PID record whose start time matches even when the live command line is unavailable, and deletes a record only when a readable command line contradicts it; it never stops a reused PID. On Windows, an unavailable command line can mean the process is elevated, so doctor names the condition and stop or setup must run from a session with the same privileges. Proxy recovery stops a listener using the shared proxy binary only
when the live process tree proves it descends from the recovering supervisor. A matching shared binary alone
never proves ownership. A failed /v1/models check gets one fresh-connection confirmation before recovery can stop an owned listener; a healthy confirmation resets recovery without stopping or starting the proxy. This matters when an agent is mid-orchestration
(e.g. dispatching Codex subagents through the gateway): do not tell the user to restart Claude Code just to
bring auth back, or you kill the session that was about to use it.
Selecting models
/model picker: rows like "GPT-5.6-sol (Codex)" and "Grok 4.5".
Typed: /model claude-gpt-5.6-sol[1m] or /model claude-grok-4.5[1m]. The picker and Sidequest catalog emit those exact ids. The suffix is stripped before routing to Codex or Grok.
lib/runtime.js's exported MODEL_WINDOW_POLICY is the sole authority for gateway backend windows, picker aliases, advertised windows, and sentry mode. GPT-5.6 Sol, Terra, Luna, and GPT-6 Astra are measured rows. GPT ids absent from the table are deliberately advertised through its explicitly unmeasured 920k default, rather than silently inheriting a window. Grok 4.5 is a measured 500k row and now has the [1m] picker alias.
Codex GPT-5.6 through the ChatGPT Codex product (the subscription login this gateway routes to, not the pay-per-token API) accepted 920,012 input tokens and refused 935,012 on 2026-09-05 through claude-code-proxy 0.1.35 (upstream 55bf0b58). The shim advertises 920000 by default. Its synthetic 413 trigger is the smaller of CODEX_GATEWAY_COMPACT_TRIGGER when set and the policy row's backend window minus 40k tokens. CODEX_GATEWAY_COMPACT_TRIGGER is a ceiling, never an override of that headroom. With the optional client autoCompactWindow cap at 325000, Claude Code compacts around 292000, so the sentry is a backstop that normally does not fire. CODEX_GATEWAY_CONTEXT_WINDOW overrides every advertised Codex window. Claude Code 2.1.261 ignores a settings-file CLAUDE_CODE_MAX_CONTEXT_TOKENS value for its own unrecognized-model resolver, so rows above 200k use their policy's recognized [1m] alias. That alias gives Claude Code a 1M client window, the closest available setting to the verified 920k backend window; it does not promise a 1M backend input limit. A lower explicit autoCompactWindow still wins. Use /context to inspect the selected model and effective cap.
Claude models (opus/sonnet/fable, with or without [1m]) keep their OWN separate native windows
and compaction limits: the shim forwards their requests byte-identically to Anthropic and never
applies Codex window advertisement or error rewriting to them. The env block pins the current
real 1M aliases (Opus, Sonnet, Fable) to [1m] ids so a gateway session on one gets its full 1M
window instead of the 200k gateway default; Haiku stays unpinned (it's 200k). An env --write-*
command resolves those aliases through the installed Claude CLI's credential-free headless probe;
SessionStart refreshes its cache after the CLI changes or the cache ages out. A failed probe keeps
the last good pin, then a shipped safe default. Set a persistent per-alias override with
pin --opus claude-opus-4-8[1m] (same for --sonnet and --fable), or use pin --opus default
to return to auto-detection. Overrides always win. pin with no arguments shows each effective
pin and whether it is overridden. Overrides live in ~/.claude/model-gateway/pins.json, outside
the plugin cache. After a pin change or Claude CLI upgrade, run env --write-project (or
env --write-user for a shared fallback) and start a new Claude Code session; changing a saved value alone cannot alter
an open session.
Do NOT set a
global CLAUDE_CODE_AUTO_COMPACT_WINDOW: it applies to both providers and can make Codex
/compact fail after history already exceeds the Codex limit.
Caution: loading a huge reference skill (e.g. claude-api, ~800k chars) in a single turn can
spike Codex context past the point proactive compaction can recover from. Prefer pulling large
references incrementally on Codex models.
The advertised catalog is a built-in list (proxy v0.1.10 serves no /v1/models). A models.json file cannot add a backend that the claude-code-proxy allowlist does not support; update the proxy through setup instead.
Claude Desktop cannot use Codex/Grok models in this version: Desktop has its own native Gateway
configuration, separate from Claude Code CLI settings, and can point at this shim's endpoint. But
installed Desktop 1.49585.0 validates every Gateway model ID client-side and rejects any
gpt/codex/non-Anthropic family marker before it reaches the picker or a session, whether the ID
came from an explicit config entry or from discovery; a terminal [1m] is stripped first and does
not change the outcome. Only a genuinely Anthropic-backed route (a real Claude alias) is usable
there. This is a Desktop-side restriction, not a Model Gateway bug, and there is no supported
workaround: do not suggest an Anthropic-named alias to disguise a Codex/Grok route, patch the
Desktop binary, substitute credentials or auth, intercept TLS, or use a global env or hosts trick.
Tell the user Desktop end-to-end Codex/Grok support is not available on this version; the Claude
Code CLI is the verified path. VS Code success has been reported by users but is not independently
verified here. This is version-specific and can be revisited if a future Desktop release removes
the model-family filter.
RC-compat and missing Codex rows: On inspected Claude Code 2.1.267, RC-compatibility points ANTHROPIC_BASE_URL at api.anthropic.com, which disables gateway model discovery. The rows disappear and cache refresh cannot restore them. Claude Code can accept and persist /model claude-gpt-5.6-terra[1m], but that client-side action does not prove a later request reaches the gateway. The current cleaner preserves canonical [1m] ids; do not present it as a fix for a reported request error. RC-compatibility makes Claude Code treat the gateway as first-party and can enable experimental message threading. The shim refuses Codex/Grok thread continuations locally with HTTP 400 because those backends have no conversation state. Nothing is forwarded or rerouted. A recognizing client drops the threading beta and resends the full message history. This is version- and experiment-limited, so do not promise one refusal per session. Normal gateway mode is the verified inference path. The reported 2.1.259 client and inspected 2.1.267 client have no verified end-to-end RC result: inspected session creation uses HTTPS while compatibility transport is HTTP. A detected hosts entry or bound listener proves local HTTP transport only. Sidequest dispatch is unaffected because it resolves its explicit route marker and never uses picker discovery.
Claude models keep working normally at the same time (passthrough path). That does not mean a
subagent invocation can mix in a gateway id freely: the Agent tool's own model parameter is a
fixed host enum (sonnet/opus/haiku/fable), independent of gateway or plugin state, and a
gateway id passed there is refused. To route one subagent to a specific gateway model, give it a
definition file instead, with a full gateway id in model: frontmatter, and omit model from the
invocation so that frontmatter applies:
---
name: luna-reviewer
description: Reviews code changes using the GPT-5.6 Luna gateway model.
model: claude-gpt-5.6-luna[1m]
tools: Read, Grep, Glob
---
Review the diff for correctness and report findings.
Save that as .claude/agents/luna-reviewer.md and start a new session before invoking it —
definitions are read at session start, so a running session won't see one just added. Invoke with
subagent_type: "luna-reviewer" and no model argument. This needs only Model Gateway, not
Sidequest: a concrete gateway id needs no route marker, only Sidequest's own claude-codex-auto id
does. Never edit Claude Code's built-in model aliases, the Agent tool schema, or the host binary —
none of that is supported or necessary. This frontmatter contract and Model Gateway's routing for a
concrete id are confirmed; an end-to-end custom-agent spawn through this path has not been verified
here.
Codex schema compatibility: Codex rejects some Unicode property escapes such as \p{Cc} and \P{Cf}. After deferred hydration, the shim changes only a Codex-bound provider hint, never Claude Code's host schema or an Anthropic request. It admits a missing dialect or Draft 2020-12 and only pattern instances reached through properties, compatible patternProperties values, additionalProperties, items, prefixItems, allOf, anyOf, dependentSchemas, propertyNames, or unevaluatedProperties/unevaluatedItems. Each real property atom must stand alone in a negated character class without ranges, set syntax, captures, backreferences, or a negative regex context. The shim removes only that atom and preserves every other regex byte. not, conditionals, oneOf, contains, references, definitions, content schemas, affected pattern-property keys, unknown containers, and unsafe regexes refuse locally with HTTP 400 naming the tool, JSON Pointer, and reason code. Tell the user it was not forwarded or rerouted. Do not claim arbitrary schemas are preserved or try to bypass that diagnostic.
Local gateway records
Request-route logging is enabled by default. It writes metadata-only JSONL records to
~/.claude/model-gateway/logs/request-routes.jsonl: timestamp, backend, model, request path, route and
effort when present, safe session and agent correlation ids, and dispatch-marker length when present. It
never writes request bodies, prompts, messages, tools, authentication, or arbitrary headers. Honor a user
request to disable it by setting CODEX_GATEWAY_REQUEST_LOG=0 before the shim starts, then restart the shim
through setup or ensure. The value is read when the shim process starts, so /reload-plugins does not
change an already-running shim. CODEX_GATEWAY_REQUEST_LOG_PATH changes the file location.
Usage observability also writes one high-water JSON file per valid session under
~/.claude/model-gateway/request-body/. The filename is derived from the session id. Its contents are the
largest forwarded request-body byte count observed for that session and an observation timestamp. It does
not contain the request body. No retention period is promised for either local record.
When the user asks about a failed Codex compaction and consent-gated route telemetry is available, inspect fixed compaction trace metadata only: selected/effective model, backend, upstream status, elapsed time, outcome, terminal/error code, and observed usage counters. completed and empty_summary both mean the generation finished: either a message_stop frame arrived, or a message_delta declared an end_turn/stop_sequence stop reason with no content after it and no content block left open. empty_summary is that finish with no visible non-whitespace text. incomplete means the HTTP-200 SSE stream never showed a finished generation: no stop reason, a stop reason that truncates (max_tokens, refusal, pause_turn, tool_use, anything unrecognized), content still arriving after the stop, an open block, or an upstream error frame. compaction_terminal_code is message_stop only when that frame really arrived, so a completed outcome with no terminal code means the frame was lost and the shim closed the turn itself instead of re-running the compaction. Unknown provider errors appear only as unknown_error. Never infer summary content or repeat prompts, response text, raw errors, headers, credentials, or environment values from diagnostics.
Claude Code's /remote-control only lights up when ANTHROPIC_BASE_URL is exactly the real
Anthropic host, which conflicts with gateway model discovery. RC-compatibility is a reversible,
opt-in local HTTP transport configuration, not a verified end-to-end Remote Control solution. At
inspected Claude Code 2.1.267 its session creation uses HTTPS while the compatibility listener is
HTTP; the reported 2.1.259 client and inspected client have no verified end-to-end RC result. A
bound port or detected hosts entry proves only that transport state. Before enabling, tell the user
that the picker rows disappear and an explicit [1m] id may be accepted client-side without proving
its later inference request reaches the gateway. Keep normal gateway mode as the verified inference
path. Do not suggest a cache refresh as a fix for a reported request error; the current cleaner
preserves canonical [1m] ids.
For the confirmation-gated procedure, use the remote-control-compatibility skill. It manages the
plugin-marked hosts block, creates a backup before an elevated write, reconciles gateway mode, and
checks the final transport state. Do not edit the hosts file outside that procedure. If effective
process env ANTHROPIC_BASE_URL is HTTPS api.anthropic.com (including port 443), enabling is
refused before any backup, hosts write, startup, or reconciliation because the loopback mapping
cannot serve TLS. A user-controlled Claude Code CLI launch can correct or unset that value, then
restart. If a host replaces it, use the supported Claude Code CLI on the wired project instead.
Desktop routing is unsupported under forced overrides on Windows and macOS, and settings, parent,
or User-scope edits cannot be promised to win. Disabling stays available.
- The user (never this plugin, never automatically) adds one hosts entry mapping
api.anthropic.com to loopback — 127.0.0.1 api.anthropic.com on Windows
(C:\Windows\System32\drivers\etc\hosts, needs Administrator), macOS, and Linux (/etc/hosts,
needs sudo). If asked to help with this, tell the user the exact line and file, and that they
need elevated privileges to save it; do not attempt to edit the hosts file yourself.
ensure/setup/doctor detect the entry (read-only) and, only after confirming the shim can
actually bind loopback port 80, switch ANTHROPIC_BASE_URL to http://api.anthropic.com and
start a second listener on port 80 next to the usual 127.0.0.1:18764. Exactly one line tells
the user to restart Claude Code when the mode changes either direction.
- Removing the hosts entry, or port 80 becoming unavailable (no permission, or something else is
using it), reverts to default mode automatically, again with one restart line.
doctor reports the hosts entry (if any), whether port 80 actually bound (and why not if it
didn't), and which mode each settings scope (user/project) is wired to.
- Test/advanced overrides:
CODEX_GATEWAY_HOSTS_FILE (custom hosts path), CODEX_GATEWAY_COMPAT_PORT
(port other than 80). Neither is needed for normal use.
Day-2 operations
... status # what's running
... doctor # binary, auth, ports, model count, settings wiring
... ensure # start whatever is down (SessionStart hook runs this with --quiet)
... stop
... env --remove # unwire Claude Code (do this BEFORE uninstalling the plugin)
doctor prints the full model-window table: backend and picker ids, backend and advertised windows,
Claude Code's resolved client window and compaction point, sentry mode and trigger, and the measurement
date. It includes Codex, Grok, and native Claude pin rows. Its model-id check is useful for stale shim ids,
but a PASS does not prove every supported model is present. A proxy from 0.1.14 through 0.1.35 can
omit GPT-6 Astra while this check passes. If Astra is missing, check the installed and serving proxy
version, rerun setup to fetch the latest release, and fully restart Claude Code. Astra requires
claude-code-proxy 0.1.36 or newer. A models.json edit cannot add a backend that the proxy allowlist
does not support. A FAIL naming missing and extra ids means the shim is stale even when its version
matches: restart it through the normal ensure or setup path, then restart Claude Code sessions so the
picker re-discovers the rows.
Logs live in ~/.claude/model-gateway/logs/. guardian.log has recovery output; lifecycle.jsonl
has bounded process evidence that doctor summarizes. Ports: shim 18764, proxy 18765 (override with
CODEX_GATEWAY_PORT / CODEX_GATEWAY_PROXY_PORT, but the env block and running processes must
agree).
Failure modes worth knowing
- Every request fails after wiring: a SessionStart hook can time out while it starts the shim. Claude Code cancels that hook, and a directly spawned supervisor can die with its process tree before it records an exit. Model Gateway launches the supervisor outside that tree and stops waiting before the hook budget, but if this is an older install or it still repeats, run
doctor, check logs. Worst case env --remove restores stock behavior instantly.
- Codex sessions drop while this plugin's suite runs: this version scopes fixture cleanup to
the test gateway home, so the suite never touches the installed gateway. If it happens after
updating, run
doctor and include its supervisor conflict line and lifecycle evidence.
- Codex models error, Claude models fine: proxy or OpenAI side. Check
login state
(doctor shows auth), then proxy log. OpenAI gates non-Codex clients by request fingerprint;
when they tighten it, requests die mid-stream until claude-code-proxy ships a fix, so
suggest re-running setup (it fetches the latest release).
doctor says upstream-unavailable: a final Codex inference failed in the last 60 seconds.
It records completed request outcomes, not /v1/models or a health check, and clears only after
a completed successful Codex response. The 60-second expiry means there is no recent failure
evidence, not that Codex is live. An upstream-blocked OpenAI/auth rejection stays separate
and does not expire; setup deliberately clears either record. Sidequest consumes a cached
catalog and can lag this state by up to five minutes.
- Startup, recovery, restart, or drain refuses to touch a listener: each ownership probe shares one
CODEX_GATEWAY_PROBE_TIMEOUT_MS budget (2 seconds by default, 8 seconds on Windows, where the Win32_Process
lookup itself typically takes 1.8-2.4 seconds). When that budget expires, the refusal says so, names the elapsed
budget, and points to CODEX_GATEWAY_PROBE_TIMEOUT_MS as the override. A malformed process result or
unrecognized command remains an ownership-unknown refusal without the timeout guidance. Startup records
owner-unknown and leaves that listener untouched; recovery leaves it for the next tick. A confirmed foreign
owner is also left untouched. Probe children are stopped with the supervisor, so they cannot keep a test fixture home open.
doctor shows Not authenticated right after an upgrade: bumping the proxy binary (e.g.
0.1.10 → 0.1.17 via setup) can invalidate the credential the old version accepted — the new
binary reads it as not authenticated and setup stops before wiring. Fix: re-run login, then
setup again to finish. Until then every Codex model is down, so any run that routes to
Codex (a whole sidequest board of Codex-tier tickets, for one) stalls entirely.
- GPT-6 Astra is missing from
/model: do not diagnose account access first. Check the installed and serving claude-code-proxy version. Astra requires 0.1.36 or newer; 0.1.35 does not include its backend allowlist, while the current doctor floor can still pass. Re-run setup to fetch the latest GitHub release, then fully restart Claude Code. Do not propose models.json: it cannot add a backend the proxy does not allow.
- No "From gateway" rows in /model: discovery is off (
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY
missing), Claude Code < v2.1.129, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is set (it
disables discovery), or RC-compatibility is active. Claude Code only refetches discovery with an
API-key credential, so OAuth users rely on Model Gateway's cache write. Run doctor to check the
cache, then restart Claude Code after it updates; /reload-plugins does not reload picker rows.
- Thinking/reasoning: the Codex backend doesn't return thinking blocks into Claude Code's
UI; that's an upstream limitation, not a bug here.
- Permission mode flips to "accept edits on" during Codex sessions: caused by GPT models
calling the plan-mode tools; an approved ExitPlanMode downgrades the mode instead of
restoring it (anthropics/claude-code#39973). The shim strips EnterPlanMode/ExitPlanMode from
Codex-bound requests since 0.2.1, so this shouldn't recur; if it does, make sure the shim was
restarted (
stop + start). Shift+Tab restores the mode in an affected session. Escape
hatch to re-enable plan tools: CODEX_GATEWAY_KEEP_PLAN_TOOLS=1.
1---2name: model-gateway3description: Set up, update, or diagnose the local ChatGPT/Codex gateway for Claude Code's /model picker. Use for gateway setup, login, model visibility, routing, or failures.4---56# model-gateway78Two local processes give Claude Code native access to the user's ChatGPT subscription models:9`claude-code-proxy` (does OpenAI OAuth and translates Anthropic Messages API to the Codex10backend) and a shim router this plugin owns. `ANTHROPIC_BASE_URL` points at the shim: requests11for `claude-gpt-*` models are un-prefixed and go to the proxy, everything else passes through12to api.anthropic.com with the user's normal claude.ai login. The shim's `/v1/models` advertises13Codex models with a `claude-` prefix because Claude Code's model discovery drops ids that don't14start with `claude`/`anthropic`. That prefix is shared with the real Anthropic ids, so the route15is decided by the backend family segment (`claude-gpt-*`, `claude-grok-*`), never by the prefix.1617All commands: `node ~/.claude/model-gateway/model-gateway.js <command>`. SessionStart recreates this stable launcher from Claude Code's installed-plugin registry, so it follows upgrades and falls back to an installed downgrade; it reports a missing install directly.1819## First-time setup2021Project-local wiring is the standard setup. `env --write-project` writes the current project's `.claude/settings.local.json`, so the gateway stays configured for this project's sessions and executor worktrees without putting a machine-local endpoint in a committed file. `setup` uses the same project-local target by default.2223`env --write-user` remains an opt-in shared fallback for people who deliberately want one gateway URL in `~/.claude/settings.json` across every project. Claude Code gives a current project's `settings.local.json` higher precedence, so `doctor` marks the winner `[effective]`, names both files, and says that project-local wiring wins when their gateway modes disagree.2425The first-run order matters:26271. Install Model Gateway at the recommended project scope.282. Reload plugins so the new skill is available.293. Invoke this skill and run `setup`.304. If setup says sign-in is needed, have the user complete `login`, then run `setup` again. The second setup finishes the download, wiring, and project confirmation.315. After the project wiring is confirmed, tell the user to fully restart the Claude Code process for this same project before selecting a model.3233A plugin reload alone does not reload settings or the picker cache. Do not tell the user to select a new row until that full restart is complete.3435The SessionStart hook injects a one-line nudge while the gateway is in any half-configured36state; act on it. The user sees that same line in the transcript, because a state only they can fix used to37reach the model alone. Anything routine stays out of it, and the hook always exits 0 so the line survives:38run `ensure` yourself when you need an exit code. SessionStart waits at most 12 seconds for a newly started39gateway, then leaves its supervisor to finish in the background so it stays inside Claude Code's hook budget. `setup` is one-shot and idempotent: it downloads the claude-code-proxy binary40(sha256-verified) and starts everything. Re-running it later is also the upgrade path. Worker hot-replacement accepts the current CLI or a non-older version-shaped sibling under the same lexical and resolved parent directory. This checks filesystem layout, not marketplace registration; a development layout with the same structure can also satisfy it.4142Setup fails before extraction when the required checksum asset is absent, its download fails, its contents are invalid or name a different archive, or the archive digest does not match. Report the verification failure; never skip the checksum check or run the unverified download.4344Use the bundled lifecycle commands for restarts and drains. They authenticate with the per-user `~/.claude/model-gateway/control-token` file. Never print or copy this secret into a conversation, request body, or settings file. Browser-origin lifecycle requests are refused, and a replacement worker must be this CLI or a non-older sibling within the same resolved plugin cache. Development checkouts cannot hot-switch to unrelated installations. After updating, restart the calling Claude Code process to load the current control protocol. These controls do not authenticate inference requests to the separate proxy or protect against a process with the same OS-user access.4546```bash47node ~/.claude/model-gateway/model-gateway.js setup48# only if setup says sign-in is needed:49node ~/.claude/model-gateway/model-gateway.js login # browser OAuth; --device for headless50node ~/.claude/model-gateway/model-gateway.js setup # finishes the wiring51```5253`login` opens the user's browser; they complete it themselves (suggest `! node ... login` if it54needs a real TTY). `env --write-project` writes this project's `.claude/settings.local.json`.55Use `env --write-user` only when the user wants a shared fallback across projects. If both are56present, project-local wiring wins: `doctor` marks it `[effective]`, names the shadowed user file,57and fails when their gateway modes differ. All wiring changes apply to new Claude Code sessions,58so restart after the write. The Codex rows appear in `/model` labeled "From gateway".5960The winning `ANTHROPIC_BASE_URL` source follows Claude Code's precedence: process environment,61current-project `.claude/settings.local.json`, project `.claude/settings.json`, then user62`~/.claude/settings.json`. A process export always wins, so settings writes cannot replace it.6364When `doctor` or SessionStart says that process env shadows a wired settings file, Model Gateway is65bypassed. If the user controls the Claude Code CLI launch, they can correct or unset66`ANTHROPIC_BASE_URL`, then restart. If the host replaces that value, use the supported Claude Code CLI67on the wired project instead. Model Gateway does not support Desktop routing under forced overrides on68Windows or macOS, and settings, parent, or User-scope edits cannot be promised to win.6970`env --write-user --reconcile` is confirmation-gated. Plain `env --write-user` writes the shared71user fallback, then lists recorded projects whose local URL differs without changing their files.72The confirmed command removes only Model Gateway-owned keys from those other projects'73`.claude/settings.local.json` files: its base URL, the three Claude alias pins, and static gateway74flags whose values equal plugin defaults. It leaves unrelated settings alone, skips projects already75agreeing, cannot change `process.env`, and needs a restart to affect a new session.76Discovery needs Claude Code v2.1.129+; `models` shows exactly what the shim advertises. Claude Code77only refetches gateway discovery when it has an API-key credential. OAuth subscriptions do not give it78one, so Model Gateway writes Claude Code's discovery cache whenever its advertised list changes.7980Restart remains necessary to surface new rows in `/model`: Claude Code reads the picker cache once at81session start. `/reload-plugins` does not reload it. Restoring or refreshing auth on an already-wired82install needs no restart of the current Claude Code process: the proxy is a separate process, so once `login` + `setup` re-authenticate it,83the next request routes through cleanly. Settings, discovery-cache, plugin, or model-row changes do need a full restart of the affected project process. Keep these two recovery paths separate. The shim supervisor also probes the proxy's `/v1/models` endpoint84while it runs, confirming a failed probe through a fresh connection before restarting an unavailable proxy with single-flight bounded backoff. It leaves a healthy proxy85alone. Recovery output remains in `~/.claude/model-gateway/logs/guardian.log`; bounded lifecycle records in86`~/.claude/model-gateway/logs/lifecycle.jsonl` identify supervisor, worker, and proxy PIDs, orderly87stop/restart requests, observed exits, and recovery outcomes. Use `doctor` to print the evidence path and88the last observed exit. An OS termination or force-killed supervisor may leave no final record, so treat an89absent exit record as absence of evidence, not a clean shutdown. Cleanup accepts a PID record whose start time matches even when the live command line is unavailable, and deletes a record only when a readable command line contradicts it; it never stops a reused PID. On Windows, an unavailable command line can mean the process is elevated, so `doctor` names the condition and `stop` or `setup` must run from a session with the same privileges. Proxy recovery stops a listener using the shared proxy binary only90when the live process tree proves it descends from the recovering supervisor. A matching shared binary alone91never proves ownership. A failed `/v1/models` check gets one fresh-connection confirmation before recovery can stop an owned listener; a healthy confirmation resets recovery without stopping or starting the proxy. This matters when an agent is mid-orchestration92(e.g. dispatching Codex subagents through the gateway): do not tell the user to restart Claude Code just to93bring auth back, or you kill the session that was about to use it.9495## Selecting models9697- `/model` picker: rows like "GPT-5.6-sol (Codex)" and "Grok 4.5".98- Typed: `/model claude-gpt-5.6-sol[1m]` or `/model claude-grok-4.5[1m]`. The picker and Sidequest catalog emit those exact ids. The suffix is stripped before routing to Codex or Grok.99- `lib/runtime.js`'s exported `MODEL_WINDOW_POLICY` is the sole authority for gateway backend windows, picker aliases, advertised windows, and sentry mode. GPT-5.6 Sol, Terra, Luna, and GPT-6 Astra are measured rows. GPT ids absent from the table are deliberately advertised through its explicitly unmeasured 920k default, rather than silently inheriting a window. Grok 4.5 is a measured 500k row and now has the `[1m]` picker alias.100- Codex GPT-5.6 through the ChatGPT Codex product (the subscription login this gateway routes to, not the pay-per-token API) accepted 920,012 input tokens and refused 935,012 on 2026-09-05 through claude-code-proxy 0.1.35 (upstream 55bf0b58). The shim advertises `920000` by default. Its synthetic 413 trigger is the smaller of `CODEX_GATEWAY_COMPACT_TRIGGER` when set and the policy row's backend window minus 40k tokens. `CODEX_GATEWAY_COMPACT_TRIGGER` is a ceiling, never an override of that headroom. With the optional client `autoCompactWindow` cap at `325000`, Claude Code compacts around `292000`, so the sentry is a backstop that normally does not fire. `CODEX_GATEWAY_CONTEXT_WINDOW` overrides every advertised Codex window. Claude Code 2.1.261 ignores a settings-file `CLAUDE_CODE_MAX_CONTEXT_TOKENS` value for its own unrecognized-model resolver, so rows above 200k use their policy's recognized `[1m]` alias. That alias gives Claude Code a 1M client window, the closest available setting to the verified 920k backend window; it does not promise a 1M backend input limit. A lower explicit `autoCompactWindow` still wins. Use `/context` to inspect the selected model and effective cap.101- Claude models (opus/sonnet/fable, with or without `[1m]`) keep their OWN separate native windows102 and compaction limits: the shim forwards their requests byte-identically to Anthropic and never103 applies Codex window advertisement or error rewriting to them. The env block pins the current104 real 1M aliases (Opus, Sonnet, Fable) to `[1m]` ids so a gateway session on one gets its full 1M105 window instead of the 200k gateway default; Haiku stays unpinned (it's 200k). An `env --write-*`106 command resolves those aliases through the installed Claude CLI's credential-free headless probe;107 SessionStart refreshes its cache after the CLI changes or the cache ages out. A failed probe keeps108 the last good pin, then a shipped safe default. Set a persistent per-alias override with109 `pin --opus claude-opus-4-8[1m]` (same for `--sonnet` and `--fable`), or use `pin --opus default`110 to return to auto-detection. Overrides always win. `pin` with no arguments shows each effective111 pin and whether it is overridden. Overrides live in `~/.claude/model-gateway/pins.json`, outside112 the plugin cache. After a pin change or Claude CLI upgrade, run `env --write-project` (or113 `env --write-user` for a shared fallback) and start a new Claude Code session; changing a saved value alone cannot alter114 an open session.115- Do NOT set a116 global `CLAUDE_CODE_AUTO_COMPACT_WINDOW`: it applies to both providers and can make Codex117 `/compact` fail after history already exceeds the Codex limit.118- Caution: loading a huge reference skill (e.g. `claude-api`, ~800k chars) in a single turn can119 spike Codex context past the point proactive compaction can recover from. Prefer pulling large120 references incrementally on Codex models.121- The advertised catalog is a built-in list (proxy v0.1.10 serves no /v1/models). A `models.json` file cannot add a backend that the claude-code-proxy allowlist does not support; update the proxy through `setup` instead.122- **Claude Desktop cannot use Codex/Grok models in this version**: Desktop has its own native Gateway123 configuration, separate from Claude Code CLI settings, and can point at this shim's endpoint. But124 installed Desktop 1.49585.0 validates every Gateway model ID client-side and rejects any125 `gpt`/`codex`/non-Anthropic family marker before it reaches the picker or a session, whether the ID126 came from an explicit config entry or from discovery; a terminal `[1m]` is stripped first and does127 not change the outcome. Only a genuinely Anthropic-backed route (a real Claude alias) is usable128 there. This is a Desktop-side restriction, not a Model Gateway bug, and there is no supported129 workaround: do not suggest an Anthropic-named alias to disguise a Codex/Grok route, patch the130 Desktop binary, substitute credentials or auth, intercept TLS, or use a global env or hosts trick.131 Tell the user Desktop end-to-end Codex/Grok support is not available on this version; the Claude132 Code CLI is the verified path. VS Code success has been reported by users but is not independently133 verified here. This is version-specific and can be revisited if a future Desktop release removes134 the model-family filter.135- **RC-compat and missing Codex rows**: On inspected Claude Code 2.1.267, RC-compatibility points `ANTHROPIC_BASE_URL` at `api.anthropic.com`, which disables gateway model discovery. The rows disappear and cache refresh cannot restore them. Claude Code can accept and persist `/model claude-gpt-5.6-terra[1m]`, but that client-side action does not prove a later request reaches the gateway. The current cleaner preserves canonical `[1m]` ids; do not present it as a fix for a reported request error. RC-compatibility makes Claude Code treat the gateway as first-party and can enable experimental message threading. The shim refuses Codex/Grok thread continuations locally with HTTP 400 because those backends have no conversation state. Nothing is forwarded or rerouted. A recognizing client drops the threading beta and resends the full message history. This is version- and experiment-limited, so do not promise one refusal per session. Normal gateway mode is the verified inference path. The reported 2.1.259 client and inspected 2.1.267 client have no verified end-to-end RC result: inspected session creation uses HTTPS while compatibility transport is HTTP. A detected hosts entry or bound listener proves local HTTP transport only. Sidequest dispatch is unaffected because it resolves its explicit route marker and never uses picker discovery.136- Claude models keep working normally at the same time (passthrough path). That does not mean a137 subagent invocation can mix in a gateway id freely: the `Agent` tool's own `model` parameter is a138 fixed host enum (`sonnet`/`opus`/`haiku`/`fable`), independent of gateway or plugin state, and a139 gateway id passed there is refused. To route one subagent to a specific gateway model, give it a140 definition file instead, with a full gateway id in `model:` frontmatter, and omit `model` from the141 invocation so that frontmatter applies:142143 ```markdown144 ---145 name: luna-reviewer146 description: Reviews code changes using the GPT-5.6 Luna gateway model.147 model: claude-gpt-5.6-luna[1m]148 tools: Read, Grep, Glob149 ---150151 Review the diff for correctness and report findings.152 ```153154 Save that as `.claude/agents/luna-reviewer.md` and start a new session before invoking it —155 definitions are read at session start, so a running session won't see one just added. Invoke with156 `subagent_type: "luna-reviewer"` and no `model` argument. This needs only Model Gateway, not157 Sidequest: a concrete gateway id needs no route marker, only Sidequest's own `claude-codex-auto` id158 does. Never edit Claude Code's built-in model aliases, the `Agent` tool schema, or the host binary —159 none of that is supported or necessary. This frontmatter contract and Model Gateway's routing for a160 concrete id are confirmed; an end-to-end custom-agent spawn through this path has not been verified161 here.162- **Codex schema compatibility**: Codex rejects some Unicode property escapes such as `\p{Cc}` and `\P{Cf}`. After deferred hydration, the shim changes only a Codex-bound provider hint, never Claude Code's host schema or an Anthropic request. It admits a missing dialect or Draft 2020-12 and only `pattern` instances reached through `properties`, compatible `patternProperties` values, `additionalProperties`, `items`, `prefixItems`, `allOf`, `anyOf`, `dependentSchemas`, `propertyNames`, or `unevaluatedProperties`/`unevaluatedItems`. Each real property atom must stand alone in a negated character class without ranges, set syntax, captures, backreferences, or a negative regex context. The shim removes only that atom and preserves every other regex byte. `not`, conditionals, `oneOf`, `contains`, references, definitions, content schemas, affected pattern-property keys, unknown containers, and unsafe regexes refuse locally with HTTP 400 naming the tool, JSON Pointer, and reason code. Tell the user it was not forwarded or rerouted. Do not claim arbitrary schemas are preserved or try to bypass that diagnostic.163164## Local gateway records165166Request-route logging is enabled by default. It writes metadata-only JSONL records to167`~/.claude/model-gateway/logs/request-routes.jsonl`: timestamp, backend, model, request path, route and168effort when present, safe session and agent correlation ids, and dispatch-marker length when present. It169never writes request bodies, prompts, messages, tools, authentication, or arbitrary headers. Honor a user170request to disable it by setting `CODEX_GATEWAY_REQUEST_LOG=0` before the shim starts, then restart the shim171through `setup` or `ensure`. The value is read when the shim process starts, so `/reload-plugins` does not172change an already-running shim. `CODEX_GATEWAY_REQUEST_LOG_PATH` changes the file location.173174Usage observability also writes one high-water JSON file per valid session under175`~/.claude/model-gateway/request-body/`. The filename is derived from the session id. Its contents are the176largest forwarded request-body byte count observed for that session and an observation timestamp. It does177not contain the request body. No retention period is promised for either local record.178179When the user asks about a failed Codex compaction and consent-gated route telemetry is available, inspect fixed compaction trace metadata only: selected/effective model, backend, upstream status, elapsed time, outcome, terminal/error code, and observed usage counters. `completed` and `empty_summary` both mean the generation finished: either a `message_stop` frame arrived, or a `message_delta` declared an `end_turn`/`stop_sequence` stop reason with no content after it and no content block left open. `empty_summary` is that finish with no visible non-whitespace text. `incomplete` means the HTTP-200 SSE stream never showed a finished generation: no stop reason, a stop reason that truncates (`max_tokens`, `refusal`, `pause_turn`, `tool_use`, anything unrecognized), content still arriving after the stop, an open block, or an upstream error frame. `compaction_terminal_code` is `message_stop` only when that frame really arrived, so a completed outcome with no terminal code means the frame was lost and the shim closed the turn itself instead of re-running the compaction. Unknown provider errors appear only as `unknown_error`. Never infer summary content or repeat prompts, response text, raw errors, headers, credentials, or environment values from diagnostics.180181Claude Code's `/remote-control` only lights up when `ANTHROPIC_BASE_URL` is exactly the real182Anthropic host, which conflicts with gateway model discovery. RC-compatibility is a reversible,183opt-in local HTTP transport configuration, not a verified end-to-end Remote Control solution. At184inspected Claude Code 2.1.267 its session creation uses HTTPS while the compatibility listener is185HTTP; the reported 2.1.259 client and inspected client have no verified end-to-end RC result. A186bound port or detected hosts entry proves only that transport state. Before enabling, tell the user187that the picker rows disappear and an explicit `[1m]` id may be accepted client-side without proving188its later inference request reaches the gateway. Keep normal gateway mode as the verified inference189path. Do not suggest a cache refresh as a fix for a reported request error; the current cleaner190preserves canonical `[1m]` ids.191192For the confirmation-gated procedure, use the `remote-control-compatibility` skill. It manages the193plugin-marked hosts block, creates a backup before an elevated write, reconciles gateway mode, and194checks the final transport state. Do not edit the hosts file outside that procedure. If effective195process env `ANTHROPIC_BASE_URL` is HTTPS `api.anthropic.com` (including port 443), enabling is196refused before any backup, hosts write, startup, or reconciliation because the loopback mapping197cannot serve TLS. A user-controlled Claude Code CLI launch can correct or unset that value, then198restart. If a host replaces it, use the supported Claude Code CLI on the wired project instead.199Desktop routing is unsupported under forced overrides on Windows and macOS, and settings, parent,200or User-scope edits cannot be promised to win. Disabling stays available.201202- The user (never this plugin, never automatically) adds one hosts entry mapping203 `api.anthropic.com` to loopback — `127.0.0.1 api.anthropic.com` on Windows204 (`C:\Windows\System32\drivers\etc\hosts`, needs Administrator), macOS, and Linux (`/etc/hosts`,205 needs `sudo`). If asked to help with this, tell the user the exact line and file, and that they206 need elevated privileges to save it; do not attempt to edit the hosts file yourself.207- `ensure`/`setup`/`doctor` detect the entry (read-only) and, only after confirming the shim can208 actually bind loopback port 80, switch `ANTHROPIC_BASE_URL` to `http://api.anthropic.com` and209 start a second listener on port 80 next to the usual `127.0.0.1:18764`. Exactly one line tells210 the user to restart Claude Code when the mode changes either direction.211- Removing the hosts entry, or port 80 becoming unavailable (no permission, or something else is212 using it), reverts to default mode automatically, again with one restart line.213- `doctor` reports the hosts entry (if any), whether port 80 actually bound (and why not if it214 didn't), and which mode each settings scope (user/project) is wired to.215- Test/advanced overrides: `CODEX_GATEWAY_HOSTS_FILE` (custom hosts path), `CODEX_GATEWAY_COMPAT_PORT`216 (port other than 80). Neither is needed for normal use.217218## Day-2 operations219220```bash221... status # what's running222... doctor # binary, auth, ports, model count, settings wiring223... ensure # start whatever is down (SessionStart hook runs this with --quiet)224... stop225... env --remove # unwire Claude Code (do this BEFORE uninstalling the plugin)226```227228`doctor` prints the full model-window table: backend and picker ids, backend and advertised windows,229Claude Code's resolved client window and compaction point, sentry mode and trigger, and the measurement230date. It includes Codex, Grok, and native Claude pin rows. Its model-id check is useful for stale shim ids,231but a `PASS` does not prove every supported model is present. A proxy from 0.1.14 through 0.1.35 can232omit GPT-6 Astra while this check passes. If Astra is missing, check the installed and serving proxy233version, rerun `setup` to fetch the latest release, and fully restart Claude Code. Astra requires234claude-code-proxy 0.1.36 or newer. A `models.json` edit cannot add a backend that the proxy allowlist235does not support. A `FAIL` naming missing and extra ids means the shim is stale even when its version236matches: restart it through the normal `ensure` or `setup` path, then restart Claude Code sessions so the237picker re-discovers the rows.238239Logs live in `~/.claude/model-gateway/logs/`. `guardian.log` has recovery output; `lifecycle.jsonl`240has bounded process evidence that `doctor` summarizes. Ports: shim 18764, proxy 18765 (override with241`CODEX_GATEWAY_PORT` / `CODEX_GATEWAY_PROXY_PORT`, but the env block and running processes must242agree).243244## Failure modes worth knowing245246- **Every request fails after wiring**: a SessionStart hook can time out while it starts the shim. Claude Code cancels that hook, and a directly spawned supervisor can die with its process tree before it records an exit. Model Gateway launches the supervisor outside that tree and stops waiting before the hook budget, but if this is an older install or it still repeats, run247 `doctor`, check logs. Worst case `env --remove` restores stock behavior instantly.248- **Codex sessions drop while this plugin's suite runs**: this version scopes fixture cleanup to249 the test gateway home, so the suite never touches the installed gateway. If it happens after250 updating, run `doctor` and include its supervisor conflict line and lifecycle evidence.251- **Codex models error, Claude models fine**: proxy or OpenAI side. Check `login` state252 (`doctor` shows auth), then proxy log. OpenAI gates non-Codex clients by request fingerprint;253 when they tighten it, requests die mid-stream until claude-code-proxy ships a fix, so254 suggest re-running `setup` (it fetches the latest release).255- **`doctor` says `upstream-unavailable`**: a final Codex inference failed in the last 60 seconds.256 It records completed request outcomes, not `/v1/models` or a health check, and clears only after257 a completed successful Codex response. The 60-second expiry means there is no recent failure258 evidence, not that Codex is live. An `upstream-blocked` OpenAI/auth rejection stays separate259 and does not expire; `setup` deliberately clears either record. Sidequest consumes a cached260 catalog and can lag this state by up to five minutes.261- **Startup, recovery, restart, or drain refuses to touch a listener**: each ownership probe shares one262 `CODEX_GATEWAY_PROBE_TIMEOUT_MS` budget (2 seconds by default, 8 seconds on Windows, where the Win32_Process263 lookup itself typically takes 1.8-2.4 seconds). When that budget expires, the refusal says so, names the elapsed264 budget, and points to `CODEX_GATEWAY_PROBE_TIMEOUT_MS` as the override. A malformed process result or265 unrecognized command remains an ownership-unknown refusal without the timeout guidance. Startup records266 `owner-unknown` and leaves that listener untouched; recovery leaves it for the next tick. A confirmed foreign267 owner is also left untouched. Probe children are stopped with the supervisor, so they cannot keep a test fixture home open.268- **`doctor` shows `Not authenticated` right after an upgrade**: bumping the proxy binary (e.g.269 0.1.10 → 0.1.17 via `setup`) can invalidate the credential the old version accepted — the new270 binary reads it as not authenticated and `setup` stops before wiring. Fix: re-run `login`, then271 `setup` again to finish. Until then every Codex model is down, so any run that routes to272 Codex (a whole sidequest board of Codex-tier tickets, for one) stalls entirely.273- **GPT-6 Astra is missing from `/model`**: do not diagnose account access first. Check the installed and serving claude-code-proxy version. Astra requires 0.1.36 or newer; 0.1.35 does not include its backend allowlist, while the current doctor floor can still pass. Re-run `setup` to fetch the latest GitHub release, then fully restart Claude Code. Do not propose `models.json`: it cannot add a backend the proxy does not allow.274- **No "From gateway" rows in /model**: discovery is off (`CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY`275 missing), Claude Code < v2.1.129, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` is set (it276 disables discovery), or RC-compatibility is active. Claude Code only refetches discovery with an277 API-key credential, so OAuth users rely on Model Gateway's cache write. Run `doctor` to check the278 cache, then restart Claude Code after it updates; `/reload-plugins` does not reload picker rows.279- **Thinking/reasoning**: the Codex backend doesn't return thinking blocks into Claude Code's280 UI; that's an upstream limitation, not a bug here.281- **Permission mode flips to "accept edits on" during Codex sessions**: caused by GPT models282 calling the plan-mode tools; an approved ExitPlanMode downgrades the mode instead of283 restoring it (anthropics/claude-code#39973). The shim strips EnterPlanMode/ExitPlanMode from284 Codex-bound requests since 0.2.1, so this shouldn't recur; if it does, make sure the shim was285 restarted (`stop` + `start`). Shift+Tab restores the mode in an affected session. Escape286 hatch to re-enable plan tools: `CODEX_GATEWAY_KEEP_PLAN_TOOLS=1`.