Claude Code
opencodex serves POST /v1/messages (plus count_tokens) alongside /v1/responses, so Claude
Code can use every routed provider — OAuth logins, account pools, key failover and sidecars
included — with zero extra auth work.
On Devin routes (including SWE-2) reached through the Messages API, text and tool calls wait for the upstream turn to complete so its late reasoning signature can precede the answer. This prevents Claude Code’s final result from becoming empty; reasoning and keepalive progress still flow during generation. The buffer shares the request’s 32 MiB translation limit and cancellation stops the producer. This output-order fix does not resolve Cognition’s separate refusal of some generated system text. It preserves system instructions and safety constraints.
For an Anthropic route on stored OAuth or an Anthropic API key, native Fast is available on
claude-opus-5-5, claude-opus-5, and claude-opus-4-8: pick the model’s --fast row (listed
when Fast rows are enabled) or set fastMode: true. Claude Code’s own /fast toggle is not
translated on routed requests: the speed field it sends does not survive translation. The proxy sends Anthropic’s Fast request and records the returned usage.speed; only
confirmed Fast usage gets the 2x API-equivalent cost estimate. A recognized Fast-specific refusal
in the main adapter request path gets one budgeted standard-speed resend, and a
new turn may incur that refused round trip again. Pro/Max subscription accounts need usage credits;
Team/Enterprise accounts need organization enablement, and API keys need provisioned
research-preview access. See
Anthropic Fast configuration
for the exact model and recovery scope.
Tier defaults for CLI and external launchers
Section titled “Tier defaults for CLI and external launchers”In the Claude Code tab, select an Opus, Sonnet, Haiku or Fable default and Save.
These claudeCode.tierModels defaults are passed to new ocx claude launches. They do not
change the currently selected main model, reasoning effort or Desktop family assignments.
Applications that launch claude directly do not receive the ocx launcher’s environment.
After saving, choose Preview Claude settings, review the ANTHROPIC_DEFAULT_*_MODEL
changes, then Apply to Claude settings. This changes the OpenCodex host’s Claude user
settings, not a remote browser’s computer. Start a new Claude process to use the new values;
the operation does not stop or restart existing sessions. It does not configure routing or
authentication, so the direct caller must already be connected to OpenCodex.
Selected routing aliases are paired with ANTHROPIC_DEFAULT_*_MODEL_NAME labels, using the
same readable names as the model picker. Preview includes both IDs and label changes; the
alias remains the submitted model ID. An existing label is managed only if it exactly matches
the generated name for its current alias. Other labels, including empty values, are preserved.
A manually entered label identical to that generated name is indistinguishable from a managed label.
Only explicitly selected tier IDs are applied. An unset tier leaves its existing model ID
and its display label untouched. Replacing an alias with a first-party
ID or another non-alias also removes its matching generated label. Preview shows label removal
as an empty replacement; custom labels and unrelated settings remain intact.
Missing files are supported, but existing empty or corrupt files are
refused. Changes since preview require a new preview. Apply briefly moves the current
document into a private model-default-backups/apply-*/settings.json directory under the
Claude config directory, validates the captured document, then publishes exclusively.
It never replaces a concurrent writer’s new file, including during rollback. Avoid editing
or launching Claude while applying: another reader can see the brief missing-file interval.
The filesystem must support same-volume moves and hard links; unsupported operations fail
without an overwrite fallback. After an interrupted apply or a failed automatic restore,
inspect this backup location and reconcile newer edits before restoring. Windows/Linux
filesystem behavior must be verified separately; a local test is not cross-platform proof.
Verify the resolved provider/model in request logs: selecting a tier is not proof of the
actual model served. This is especially useful for external classifiers requesting Sonnet.
Claude OAuth account pool (experimental)
Section titled “Claude OAuth account pool (experimental)”Native Anthropic subscription passthrough retains the upstream anthropic-ratelimit-*
response headers on streaming, JSON and upstream-error responses, so compatible Claude Code
statusLine consumers can read the provider’s quota observations. Missing observations are
not filled with invented values. This relay does not change account selection or retry behavior.
You can log in multiple Claude accounts via the Providers dashboard (ocx login anthropic /
add-account). By default every request uses the active account only.
An experimental, opt-in Claude account pool (anthropicAccountPool.enabled) adds sticky
session affinity and usage-aware new-session selection across those OAuth accounts. It does
not gate 429 failover: with two or more usable accounts stored, a rate-limited request moves
to another account whether the pool is on or off, and the toggle does not switch that off; pause
an account to keep it out of failover. For new
sessions,
anthropicAccountPool.strategy selects among eligible accounts: quota (default) picks the
lowest known usage in the window set by quotaWindow (five-hour by default, or weekly /
max-utilization) when above autoSwitchThreshold; round-robin spreads evenly
(stickyLimit, default 1); fill-first drains the active account until cooldown,
reauthentication, or threshold, then advances. It is off by default and experimental.
The dashboard lists the conditions the pool is meant for: subscriptions you own or are authorized
to use, the genuine Claude Code client, and a person supervising the session. Anthropic has not
endorsed automated account pooling, accounts in one organization may share a quota (so another
account may not add capacity), and switching accounts does not protect against provider
enforcement. OpenCodex sends no keep-warm requests and, by default, neither refreshes Claude tokens
nor reads usage in the background: usage is read when the dashboard, the menu bar app, or an ocx
command asks for it. Thresholds are selection preferences, not usage or billing caps. This is
product guidance, not legal advice; check Anthropic’s current terms.
To bind a model to particular stored Claude accounts, add ordered anthropicAccountPool.routes rules while the pool is enabled. Each rule has a safe name, a full case-sensitive match glob, an accounts array of stored account IDs, and optional fallback (default false). The first matching rule limits active, manual, affinity, strategy and 429 recovery picks to its accounts. A healthy affinity outside that rule is ignored for this request but kept for other models; the routed choice does not overwrite it. Without fallback, an empty route returns a local 401, or 429 with Retry-After when all its declared accounts are cooling, before contacting Anthropic. The client response does not name the route; the proxy log records route:#<n>, where n is the rule’s 1-based position. fallback: true uses the ordinary pool only when the route has no eligible account, including its ordinary fill-first successor order; if its stored accounts are all cooling, the returned 429 uses the earliest cooldown across that expanded pool, even if a saved route account has been removed. An unmatched model follows the existing pool policy; disabling the pool leaves saved rules inactive and restores active-account and presence-driven 429 behavior. A rule is an operator allowlist, not proof of model entitlement.
Operational contract when enabled:
- Upstream 429 cools that account, clears its affinities, and may rotate to another eligible
account within the same request (bounded). The cooldown uses a usable
Retry-Afterwhen present, otherwise the latest valid reset time among windows Anthropic marksrejected, including weekly windows. Valid upstream deadlines are not shortened to a fixed cooldown ceiling. A refusal with no usable deadline falls back to a 60-second default backoff. - Responses report the serving account’s 5-hour and weekly utilization, and whichever of those two the response carries is recorded for that account — each window independently, and a refusal counts as well as a success. Usage-aware selection works from ordinary traffic, without waiting for a dashboard poll. Headers preserve model-specific quota windows and do not postpone usage probes or clear a failed usage probe’s unavailable status. Measurements whose known reset time has passed are discarded as unknown, including retained model-specific windows. Values without a known reset are preserved; missing data is never reported as zero usage.
- Affinity is process-local (lost on proxy restart).
- A complete, structured 403 account-entitlement or billing refusal can rotate before
output. Recognized cases include no Claude Code access, an expired/inactive subscription,
and an insufficient Anthropic credit balance. The refused account loses its affinities and
cools for
Retry-After, or ten minutes without a deadline. Generic permission, model/resource access, policy and unrecognized errors stay terminal. Recovery respects model routes and send limits; if no replacement is eligible, the original 403 is returned. This also works with proactive pooling off. A 403 after assistant output starts never switches accounts. - Before output, an exact structured 401 authentication_error with no error code and the message “OAuth access token has been revoked.” marks the sending OAuth account as requiring a new login and clears its session affinities. Before output, an eligible account in the same pool may take over within existing send limits. With no eligible replacement, the original 401 is returned; the refused account remains excluded until login. Other 401 errors retain their existing behavior.
- Token-refresh credential failures retain the existing
needsReauthpolicy. Subscription renewal does not require reauthentication, but the account waits for its cooldown to expire. - If every eligible account is cooling, the proxy returns 429 (not 401) with
Retry-Afterwhen known. - Recovery, including 429 failover, uses
quotaWindowto rank eligible replacements without changing the existing cooldown or failover limits;round-robinignoresquotaWindow. autoSwitchThreshold: 0turns off proactive usage-based switching only. New-session selection and 429 recovery still consultquotaWindow, so the window is inert only underround-robin.fill-firstevaluates its drain threshold in the selected window.
See Configuration.
Native Messages with account pooling
Section titled “Native Messages with account pooling”Managed native Messages show validated caller effort in Logs and usage.jsonl, including each
attempt: an explicit effort such as xhigh or low, none for disabled thinking, or
budget:<tokens> for an enabled thinking budget. An explicit effort takes precedence over the
thinking fallback. Adaptive thinking without an explicit effort stays blank. These labels describe
request controls; they do not confirm what effort the upstream applied or change the wire body.
An enabled Anthropic account pool prefers native Messages for eligible direct Anthropic routes
when neither native rollout flag explicitly disables that path. In Providers → Anthropic →
Account pooling → How account selection works, Preserve native Claude requests stores
anthropicAccountPool.nativeMessages (default true). Turning it off selects the legacy bridge
for pooled requests. With pooling off, the explicit native rollout settings keep their behavior.
Explicit protocols.rollout.managedMessagesNative: false disables both native paths;
managedMessagesNativeOAuth: false disables native OAuth. Invalid present settings fail closed.
The checkbox shows the saved preference, so an enabled checkbox does not override these flags
or a route that requires proxy translation. Native requests retain history/cache breakpoints,
session affinity, model routes, pause/cooldown exclusions and bounded pre-output recovery.
Recognized native CLI and Desktop Code requests retain their billing/identity preamble and
supported feature beta headers. Generated requests keep the SDK identity shape. Declared custom
tools use consistent names across deferred references and inline additions/removals, while
arguments, schemas and cache markers remain unchanged. Ambiguous declarations are rejected.
An account switch may start a cold cache; this does not transfer caches or guarantee cache hits.
For inline tool changes in system messages, send the required inline-tools-2026-09-15 beta;
the first-party native builder preserves it with typed blocks independently of client recognition.
See Claude’s inline tool contract.
Quickstart
Section titled “Quickstart”ocx claudeocx claude ensures the proxy is running, then launches Claude Code with the environment wired:
| Variable | Value |
|---|---|
ANTHROPIC_BASE_URL |
http://127.0.0.1:<port> |
ANTHROPIC_AUTH_TOKEN |
Only when the proxy requires an API key — otherwise it is NOT set, so your claude.ai login (subscription + connectors) stays active |
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY |
1 (native /model picker discovery) |
CLAUDE_CODE_AUTO_COMPACT_WINDOW |
Auto-context compaction threshold (default 829800); only injected when auto-context is enabled |
ANTHROPIC_MODEL |
claudeCode.model (optional) |
ANTHROPIC_DEFAULT_HAIKU_MODEL |
claudeCode.tierModels.haiku ?? claudeCode.smallFastModel (optional; legacy ANTHROPIC_SMALL_FAST_MODEL too) |
ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE}_MODEL |
claudeCode.tierModels.*; on a subscription launch an unset slot gets the native claude-opus-5-5[1m] / claude-sonnet-5-5[1m] / claude-fable-5-1[1m], so --model opus, sonnet and fable keep their 1M context |
ANTHROPIC_DEFAULT_{OPUS,SONNET,FABLE,HAIKU}_MODEL_NAME |
The /model label for a tier slot that OpenCodex sets to a routed alias, such as gpt-6.1-sol (native) for ocx-claude-native--gpt-6.1-sol. Without it, Claude Code labels that row with the alias. The alias stays the selected model id. When system env is on, the launchd environment and the shell hook get the same slot and label pair. A slot or label you set yourself is left alone |
CLAUDE_CODE_ALWAYS_ENABLE_EFFORT |
1 when alwaysEnableEffort is on (conditional) |
ENABLE_TOOL_SEARCH |
claudeCode.toolSearch when set (conditional; off by default — see MCP tool schemas fill the context) |
CLAUDE_CODE_MAX_CONTEXT_TOKENS |
Legacy context override when maxContextTokens is set (conditional) |
Variables you export yourself always win. Extra arguments pass through: ocx claude -p "hello". |
One exception is about where a variable comes from, not about precedence. The bundled Bun
runtime auto-loads a project .env / .env.local, so a stray ANTHROPIC_API_KEY in the
directory you happen to launch from used to look identical to a deliberate export — and it
silently disabled a healthy claude.ai subscription in favour of API billing. ocx claude now
ignores Anthropic credentials that only a project dotenv introduced. A value you exported in
your shell still wins, in every auth mode. To use an API key deliberately, export it
(export ANTHROPIC_API_KEY=...) rather than leaving it in a project file.
Native fallback when Claude routing is off
Section titled “Native fallback when Claude routing is off”ocx claude used to exit with an error when Claude routing was disabled. It now launches the
native claude binary instead, so the command stays useful with routing off:
| Where routing is off | What happens |
|---|---|
claudeCode.enabled: false in config |
Native launch, with a notice that routing is disabled |
The running proxy reports enabled: false from GET /api/claude-code |
Native launch; re-enabling starts interception on demand |
claudeCode.enabled absent or true |
Routed through the proxy, unchanged |
Only an explicit false triggers the fallback, so a proxy predating the field stays routed. A
missing proxy is not a trigger either — with routing on, ocx claude still starts the proxy.
A native session must not inherit proxy state, so the fallback removes values it can prove
OpenCodex owns: ANTHROPIC_BASE_URL only when it points at this proxy’s own loopback address
and configured port and the paired admission token is one the proxy issued; the
CLAUDE_CODE_* discovery and auto-context levers; and model slots that only resolve through the
proxy (routed aliases and provider/model ids). Anything else is yours and is preserved — an
unrelated http://localhost:8080 gateway and your own sk-ant- credential both survive.
If your saved /model picker default is a proxy-only model, the native session falls back to
claudeCode.model when that is natively usable, and otherwise warns you to pass
--model <Anthropic model>. An explicit --model argument always wins.
Auth mode
Section titled “Auth mode”Claude Code needs a token in ANTHROPIC_AUTH_TOKEN to talk to a gateway, but setting that
variable also disables your claude.ai login and its connectors. Which of the two you want
depends on something opencodex can look up, so by default it does.
Leave Auth mode on Auto (the default) in Connect → Claude and opencodex decides at each launch:
| What it finds | What it does |
|---|---|
A Claude login (~/.claude.json OAuth account, .credentials.json, the macOS keychain, or an exported ANTHROPIC_API_KEY) |
Leaves the token unset, so your subscription and connectors keep working |
| No Claude auth at all | Injects a placeholder token, so Claude Code stops asking you to log in and routes through the proxy |
| It cannot tell (unreadable keychain, corrupt file) | Assumes subscription and prints a warning — it never moves a paying subscriber onto the proxy on a failed read |
This is recomputed every launch, not remembered, so logging in or out is picked up on the
next ocx claude with nothing to reconfigure.
Pick Subscription or Proxy explicitly when you want it fixed. An explicit choice is
stored in claudeCode.authMode and detection never overrides it — including after you log
in or out later. Switch back to Auto to hand the decision back.
On macOS, auto-connect (claudeCode.systemEnv) follows the same resolution, so a plain
claude launched outside ocx behaves the same way. That file is a snapshot refreshed when
the proxy starts or you save settings, while ocx claude always resolves live.
System environment integration (macOS)
Section titled “System environment integration (macOS)”Claude Desktop modes: gateway (default) and first-party
Section titled “Claude Desktop modes: gateway (default) and first-party”Claude Desktop can use OpenCodex in one of two mutually exclusive modes. Pick it in
Connect → Claude Desktop → Connection mode in the dashboard or with ocx claude desktop apply --first-party|--gateway.
Desktop library profiles and
_meta.jsonmust be regular files of at most 4 MiB. OpenCodex also refuses generated profile or metadata output above that limit before replacing files. Back up and reduce oversized configuration manually; a size refusal does not truncate the existing data. Connection-owned stores retain their existing tighter limits.
Gateway (default)
Section titled “Gateway (default)”New installs use gateway by default. Its third-party profile, described below, switches the
whole app to OpenCodex as its inference gateway. Chat runs locally through OpenCodex and the
claude.ai-only features are unavailable. The legacy --static / --hybrid /
--discovery-only flags also select gateway.
Gateway profiles use distinct model aliases with a claude-fable-5- prefix so
Claude Desktop 2.31226 can offer its effort control without treating every routed
model as the same active model. These aliases do not change the upstream model or
your displayed labels and family defaults. Previous aliases remain decodable for
existing sessions. Reapply the gateway profile and fully quit/reopen Desktop to
load the new list. The static picker uses Desktop’s fallback effort menu; it does
not advertise an authoritative per-provider ladder, and the selected provider’s
normal effort normalization still applies. This remains a client-version-specific
compatibility mechanism.
Gateway apply refuses unreadable or non-regular configuration files, including dangling metadata
links, before creating a new profile. If the metadata update fails, apply restores the profile’s
previous contents, or removes a profile it had just created, but only while the profile still holds
what apply wrote; if another program changed it in the meantime, apply keeps that version and
reports “profile rollback skipped”. Apply rechecks the profile before
backup and replacement; if it detects a concurrent edit, it refuses with desktop_profile_changed.
Review the edit and reapply. If restoring the profile also fails after a metadata error, the
reported reason includes both failures; reconcile the profile and metadata before retrying.
First-party (opt-in)
Section titled “First-party (opt-in)”Desktop itself is not reconfigured: it stays
signed in to claude.ai, and the Chat tab, connectors, cloud sessions and remote control keep
working. OpenCodex only writes these variables into the env block of ~/.claude/settings.json
(honoured by CLAUDE_CONFIG_DIR):
{ "env": { "HTTPS_PROXY": "http://opencodex:<per-install token>@127.0.0.1:10200", "NODE_EXTRA_CA_CERTS": "<home>/.opencodex/claude-intercept/ca.pem", "NO_PROXY": "127.0.0.1,localhost,::1,0.0.0.0" }}NO_PROXY keeps loopback traffic, such as a local HTTP MCP server on 127.0.0.1, localhost,
a *.localhost name, ::1 or 0.0.0.0, off the proxy, which only tunnels HTTPS to remote
hosts. A server on another 127.x address needs that address added to NO_PROXY yourself.
If you already set NO_PROXY, OpenCodex appends
only the missing loopback entries and records its change in opencodex-no-proxy.json next to
settings.json. Turning first-party off puts your original value back when OpenCodex could
record it and you have not changed it since; otherwise the current value, including the added
entries, is kept.
Claude Desktop first-party routes its Code tab and subagents through OpenCodex. The standalone Claude Code CLI has a separate first-party switch. Both clients read the same ~/.claude/settings.json proxy and CA settings: if only one switch is on, the other client still transits the local proxy, where TLS terminates, but its Messages requests relay to Anthropic unchanged. Other Anthropic paths relay unchanged and unrelated hosts remain blind tunnels. With the CLI switch on, the standalone claude also lists routed models in /model; see Model picker in the CLI.
Subagents on routed (non-Claude) models do not use Claude Code’s server-side message threads, because only Anthropic stores that state. OpenCodex declines a threaded request for such a model, and Claude Code resends that turn, and the turns after it, with the full conversation.
Mode is persisted as claudeCode.desktopMode. Installs that already applied either mode retain it,
including first-party installs from before the mode was persisted. An explicit mode takes priority;
otherwise an owned selected gateway row, an applied gateway fingerprint, or owned first-party
settings in ~/.claude/settings.json determine the existing mode before the gateway default.
A catalog sync or roster update never writes a gateway profile over a resolved first-party install.
A CLI-only first-party env is not evidence that Desktop is in first-party mode.
Switching first applies the replacement,
then removes the other mode’s configuration (only values OpenCodex wrote — a foreign HTTPS_PROXY or
NODE_EXTRA_CA_CERTS, for example a corporate proxy, is never overwritten and the apply is
refused instead). A failed replacement preserves the previous connection. If retiring the old
configuration fails after the replacement was written, the command reports incomplete cleanup;
resolve that error before restarting Desktop. A committed gateway keeps its saved mode and profile
marker even when first-party settings cleanup fails. Fully quit and reopen Desktop after a successful switch. ocx ensure refreshes a stale
first-party env when the integration is ON and removes it when OFF. Set
claudeCode.intercept.enabled: false to disable the proxy entirely; first-party then cannot be
applied. An apply for an existing first-party install is refused when the intercept is disabled;
new installs apply gateway. On a connected client the proxy runs on the
hub, so ocx claude desktop apply there uses the gateway profile.
Picker mode: opencodex models in the first-party Code-tab picker
Section titled “Picker mode: opencodex models in the first-party Code-tab picker”Picker mode is part of first-party mode. On macOS and Windows it is on by default when first-party is selected,
unless claudeCode.intercept.picker: false is set. It changes the first-party Desktop Code-tab picker
so it lists available opencodex models by name. The first time it is enabled, macOS may ask you to
trust a local certificate authority in the login keychain, and Windows may ask you to trust it in the current-user Root store. That authority is constrained to claude.ai
and its subdomains. OpenCodex protects its exportable signing identity in the OS credential store
and reuses the same validated certificate and key across normal restarts. No plaintext picker signing
key is stored in the OpenCodex config directory. The full constrained CA validation and OS trust
verification still apply, including the exact fingerprint with a verified leaf on Windows. With the same approved identity and an available credential store, restarting
OpenCodex does not add or remove macOS Certificate Trust Settings. Restart and status never install trust:
if trust is missing, revoked or unknown, the picker stays pending. Run ocx claude desktop picker on
or ocx claude desktop picker trust explicitly to grant trust, including the Windows confirmation when needed.
Startup also attempts to remove a legacy on-disk picker signing key before checking whether
interception is enabled. Cleanup is best-effort and does not enable interception or block startup.
One-time migration from an older picker identity may require consent to remove its previous trust.
If cleanup cannot finish, the picker stays unavailable and the applied profile uses a blind relay
until cleanup succeeds; ocx claude desktop picker status reports that state. macOS may separately
ask you to unlock the keychain or approve an application’s access to stored credentials. These native
access prompts can still occur on restart or upgrade. On Windows, explicit on or trust may show a confirmation to add the root.
Picker mode remains unsupported on Linux: OpenCodex starts no picker CA, credential-store or proxy work there. The main Claude intercept remains available with ownership, symlink, file-permission and Windows ACL checks protecting its local CA files.
Picker mode allows up to 64 KiB of headers on incoming requests and ordinary HTTP
responses, preserving browser session cookies. Larger upstream response headers return
502 and log upstream:headers-too-large, without cookie values or request paths.
Upgraded connections continue to relay bytes directly after the request handshake.
Picker mode uses HTTP/2 with Claude Desktop so long-lived chat streams no longer use up
Desktop’s connections to claude.ai. Earlier versions could leave chat stuck on
“Timed out loading session” while picker mode was on. If chat stops loading with picker mode
on, turn it off with ocx claude desktop picker off and report the problem.
While picker mode is on, Claude Desktop reaches the network through OpenCodex. If OpenCodex stops,
Desktop is offline until you fully restart it or turn picker mode off. Check the state with
ocx claude desktop picker status; use ocx claude desktop picker trust to repeat the trust step,
or turn it off with ocx claude desktop picker off. The dashboard has the same picker toggle under
Connect → Claude Desktop. After the picker profile is selected, fully quit and reopen Claude Desktop.
Picker mode is part of first-party mode, so the first-party account risk applies to it as well. Desktop and CLI catalog rewrites share bounded row and metadata limits: if adding routed models would exceed a limit, OpenCodex returns the original Anthropic catalog unchanged rather than publishing a partial list.
Use opencodex models from the Desktop Code tab (first-party bindings)
Section titled “Use opencodex models from the Desktop Code tab (first-party bindings)”In first-party mode the Code tab’s model picker belongs to claude.ai: its rows (Opus 5.5, Sonnet 5, Haiku 4.5, and the older models under More models) come from your account, and no local setting can add an opencodex row. What reaches OpenCodex is the picker’s Anthropic model id on every request, so you bind a picker row to an opencodex route instead:
ocx claude desktop bind claude-sonnet-4-6 xai/grok-4.7ocx claude desktop bind claude-opus-4-6 native/gpt-6.1-solocx claude desktop unbind claude-opus-4-6or use Connect → Claude Desktop → Code tab model bindings in the dashboard. Picking Sonnet 4.6 in
the Code tab is then served by xai/grok-4.7. The picker keeps Anthropic’s label, and the model is
still introduced to itself as that Claude model by Claude Code’s system prompt, so prefer rows you
do not otherwise use (the More models entries are good candidates). Bindings take effect on the
next request; Desktop does not need a restart.
- Routes use the Desktop route vocabulary:
provider/model, ornative/<slug>for the native OpenAI pool. A route must be one the dashboard lists as available. - A dated picker id (
claude-haiku-4-5-20251001) matches an undated binding (claude-haiku-4-5), and[1m]and fast-mode selections follow the same binding. - Bindings are stored in
claudeCode.intercept.modelMapand apply only to Claude Code traffic that arrives through the local intercept proxy: Desktop’s Code tab and the standaloneclaudeCLI in first-party mode.ocx claudesessions and the public/v1/messagesendpoint ignore them; the globalclaudeCode.modelMapstill applies everywhere, and a binding wins over it for the same id. ocx claude desktop status --jsonreports the bindings in effect underfirstParty.modelBindings.
Claude Code CLI first-party
Section titled “Claude Code CLI first-party”Turn on the CLI switch in Connect → Claude, or run ocx claude config set --first-party on; use off to disable it. The switch is immediate and refuses {enabled:false, cliFirstParty:true} before any field is saved; it may also refuse to turn on if the local intercept is unavailable, the CA cannot be prepared, settings cannot be read, or a foreign proxy setting owns the keys. Off persists even when the intercept is unavailable; disabling Claude routing alone leaves an owned settings env untouched. For fully native terminal traffic with only Desktop first-party on, set NO_PROXY='*' in the shell. This still carries the first-party account risk stated above.
Disabling Claude routing leaves the owned settings env untouched. While the bound listener still runs, every Messages request relays unchanged; after it stops, plain claude cannot connect until OpenCodex runs or Desktop/CLI first-party is turned off. Native ocx claude sets NO_PROXY=* only for an owned env without a foreign inherited HTTPS_PROXY/https_proxy. With a foreign proxy it preserves that value and warns that the settings-owned intercept still applies; turn Desktop/CLI first-party off or unset the setting.
The UI distinguishes uncertainty about whether settings still point at its proxy (unknown), a token-bearing opencodex proxy with a foreign CA (foreign: fix HTTPS_PROXY / NODE_EXTRA_CA_CERTS manually), and a tokenless loopback proxy beside a foreign CA (local: ownership is unconfirmed; remove HTTPS_PROXY if unused). With matching applied settings and a bound listener but Claude routing off, disabled means requests relay unchanged while it is bound; turn first-party off to remove settings. An owned URL with no listener is stopped; a bound listener with an owned CA but mismatched port or token is broken even when routing is off. With an intent on, stopped or broken plus ineligible interception displays routingOff: Claude routing or the intercept is off, or this machine is a client of another opencodex hub; enable interception on this machine or turn first-party off to remove the settings. When interception is stopped, use Start interception or ocx claude intercept start to retry within the running OpenCodex service. Saving first-party settings also starts it automatically. A refusal reports disabled routing, client role, an ephemeral public port, an occupied port, or a startup failure. For an occupied port, free it or set claudeCode.intercept.port. Startup refreshes an existing managed Claude proxy environment only after the intercept proxy binds successfully, using its bound port; an occupied port leaves Claude settings unchanged. If a pair is already bound on a different port, the response names both ports; use the bound port or restore the configured value. CLI intent with no proxy is not applied; one intent with a live proxy gets the shared-relay notice; any remaining proxy with neither intent is residual, unless unknown, foreign, or local takes precedence.
- Gateway discovery (
/model→ “From gateway”) is not used, because Claude Code only queriesGET /v1/modelson a configured gateway. Routed models reach the picker another way; see Model picker in the CLI. You can also bind a built-in Anthropic model id to a route (ocx claude desktop bind, above) or usemodelMap. ANTHROPIC_SMALL_FAST_MODELandCLAUDE_CODE_SUBAGENT_MODELare chosen by the CLI before the request is sent. Plain first-partyclaudeuses yoursettings.jsonvalues. Routedocx claudealso offers the subagent force setting on the Subagents page and through the CLI.ocx claudeand first-party coexist: a session started withocx claudetalks toANTHROPIC_BASE_URL(plain HTTP on loopback), whichHTTPS_PROXYdoes not cover, so that process reaches OpenCodex directly and the proxy simply sees no traffic from it.- Claude Code honours
HTTPS_PROXY/NODE_EXTRA_CA_CERTSas documented for corporate proxies; a CLI release that stops doing so would stop routing, not break login.
Model picker in the CLI
Section titled “Model picker in the CLI”With the CLI first-party switch on and Claude routing enabled, start a new claude and open
/model. Your opencodex models appear next to the Claude models from your account, labelled
like “Grok 4.7 (xai)” and described with their route (opencodex · xai/grok-4.7). Picking one
routes that session to the model, just like a binding.
- Only new
claudesessions pick up the list. Restart any session that was already running when you turned the switch on or off. - Claude Code saves the list for about an hour. Turning the switch on or off, and
ocx ensure, clear that saved copy, so the nextclaudelaunch fetches a fresh one. If the routed models are missing (the first launch after OpenCodex starts can miss them while models are still being discovered), runocx ensureand restartclaude. - The rows use Claude-style ids (for example
claude-opus-4-8-…), because the CLI only offers ids shaped like Claude models. Claude Desktop’s Code tab is unaffected; it keeps using first-party bindings.
Claude Desktop profile (gateway mode)
Section titled “Claude Desktop profile (gateway mode)”The profile below is written only in gateway mode. Claude Desktop uses a separate profile from Claude Code. Open Connect → Claude Desktop in the dashboard. The Models card has the two choices most people need: Default model is listed first in Claude Desktop and sent as the Opus tier, and Quick task model answers Desktop’s Haiku-tier requests. Picking a model there moves it into that family and makes it the family default. To place every route yourself, open Advanced: Claude tier assignment, where each available route sits in one of four families: Opus, Fable, Sonnet, or Haiku. All routes start in Opus on a new profile. The first Opus route becomes the initial overall default, and every non-empty family always has one family default.
Drag a row to another family if you like. Dragging is optional: every row also has a visible move control that works with a mouse, touch, or keyboard. Use Make default to choose a family’s default, then select Save and apply to Desktop. Empty families are allowed. If a saved default is temporarily unavailable, the first available route in that family is used until it returns.
You can also manage the same profile from the command line:
The profile-editing instructions below describe the local profile. Connected remote apply is described separately below.
ocx claude desktop [apply]ocx claude desktop show [--json]ocx claude desktop status [--json]ocx claude desktop move <route> <opus|fable|sonnet|haiku> [--default]ocx claude desktop default <opus|fable|sonnet|haiku> <route|none>ocx claude desktop export <path|->ocx claude desktop import <path> [--apply]ocx claude desktop and apply apply the selected mode: first-party writes the Claude Code proxy
env, gateway writes the current profile to Claude Desktop. show gives a
readable summary; status reports the effective mode, the applied profile or proxy env, drift,
request activity, and (gateway mode only) Windows managed-policy health. Add --json for scripts. export - writes versioned JSON to standard output.
Import validates the complete file before saving, so an invalid file leaves the current profile
unchanged. Add --apply to write a valid imported profile to Desktop immediately. Use none only
for an empty family; every non-empty family must keep one default.
On Windows, a machine-managed Claude policy can make Desktop ignore the local third-party profile.
OpenCodex reports this as present; a policy it cannot read is unknown, which is also a warning
rather than a clean result. The diagnostic reports only that state—it does not expose policy value
names or data, and it never removes or bypasses policy. Resolve the policy with your administrator,
then fully quit and reopen Claude Desktop. Applying again also preserves profile keys that OpenCodex
does not own while refreshing its gateway and model fields.
Apply writes to Claude Desktop’s real Electron user-data configLibrary: ~/Library/Application Support/Claude/configLibrary on macOS, %APPDATA%\Claude\configLibrary on Windows, and
${XDG_CONFIG_HOME:-~/.config}/Claude/configLibrary on Linux. Set
OPENCODEX_CLAUDE_DESKTOP_CONFIG_DIR for an explicit library override or
CLAUDE_USER_DATA_DIR for an alternate Desktop user-data root. The legacy Claude-3p directory is
not read or deleted automatically.
Non-Anthropic routes receive stable aliases such as claude-fable-5-p01q, with a p-prefixed
four-character code. Previous claude-opus-4-8-p... profile IDs remain decode-only compatibility
IDs; new profiles use the Fable form. OpenCodex keeps a separate synthetic date slot internally so existing profile
assignments stay stable, but it does not expose that date as the Desktop model id: current Desktop
builds strip terminal dates when comparing active-session models, which can suppress model changes.
Real Anthropic Claude routes keep
their real ids. New routes default to the Opus family, but moving a route does not change the
provider or model it calls. The legacy apply flags --static, --hybrid, and --discovery-only
remain available for existing scripts.
System Environment Integration
Section titled “System Environment Integration”When claudeCode.systemEnv is set to true (default: off), ocx start uses launchctl setenv
to inject ANTHROPIC_BASE_URL and the related Claude Code environment variables system-wide.
New terminal windows and tabs therefore route plain claude commands through the proxy without
requiring the ocx claude wrapper. Already-open shells are unaffected and must be reopened.
Changing a model slot or lever (smallFastModel, tierModels, maxContextTokens, auto-context,
autoCompactWindow, alwaysEnableEffort) re-applies the injection right away: a key opencodex injected earlier is
updated, or unset once the setting no longer produces it. A value you set yourself with
launchctl setenv before opencodex injects that key is never overwritten or removed; a key
opencodex injected stays opencodex-owned (refreshed, unset, and removed by ocx stop) even if
you change its value by hand.
ocx stop and proxy shutdown unset the injected keys (it does not restore previous values —
only the keys opencodex injected are removed). The proxy also writes ~/.opencodex/claude-env.sh;
ocx start installs a .zshrc source hook that loads it automatically only when an executable
Claude Code CLI is present on PATH. Startup and ocx ensure remove the OpenCodex-owned hook when
Claude Code is absent or system environment integration is inactive. Claude Desktop uses its
separate profile and does not cause shell-hook installation.
Disable with claudeCode.systemEnv: false in the configuration or with the GUI toggle. This
feature is macOS-only; on other platforms, use ocx claude.
Native Claude passthrough (subscription pierce)
Section titled “Native Claude passthrough (subscription pierce)”With no auth override set, Claude Code keeps its claude.ai OAuth login and sends it to the proxy.
Requests for genuine claude*/anthropic* models that no alias or model map claims are forwarded
verbatim to api.anthropic.com with your credential — betas, thinking signatures, prompt
caching and billing identity stay fully native, and routed models keep working in the same session
via the picker aliases.
Header handling: hop-by-hop headers plus host, content-length, accept-encoding,
x-opencodex-api-key, and origin are always stripped before forwarding. On a non-loopback bind,
native passthrough also requires a valid proxy credential in x-opencodex-api-key; Authorization
and x-api-key then belong only to Anthropic. A proxy admission secret found in either provider
header is removed, while a genuine provider credential in the other header is preserved. Ambiguous
comma-joined credential headers are not forwarded.
The passthrough fires when all of these conditions are met: nativePassthrough is not false;
the model begins with claude or anthropic; the bearer token or x-api-key starts with sk-ant-;
alias/model-map resolution returns the same model unchanged; and, on a non-loopback bind, the
dedicated proxy admission header is valid. This also means the
“claude.ai connectors are disabled” warning no longer appears with ocx claude.
The one change to the body is tool-call ids. A tool_use.id or tool_result.tool_use_id that Anthropic would reject (characters outside a-zA-Z0-9_-, or longer than 64), such as one a routed model minted earlier in the session, is rewritten to a conforming id with its call/result pairing kept. Conforming ids are sent unchanged, and an empty id is answered with a local 400.
Disable with claudeCode.nativePassthrough: false; point elsewhere with
claudeCode.anthropicBaseUrl.
Claude Desktop on a connected remote hub
Section titled “Claude Desktop on a connected remote hub”When this machine is connected to a hub, ocx claude desktop apply (or ocx claude desktop)
uses the hub’s Desktop model snapshot. It writes the connected hub origin and the hub-issued
model IDs into the local Desktop configuration without generating replacement aliases locally.
Static and hybrid modes copy the snapshot entries; discovery-only mode uses the hub origin
without embedding the model list.
The hub owns the Desktop profile, family assignments and defaults. Change those on the hub,
then apply again on the connected client and reselect the model in Desktop. Old aliases created
only on the client require reapply/reselection; they are not automatically migrated. Local show,
profile edits, and import/export remain local views and operations, not hub-profile management.
While connected, ocx claude desktop import <path> --apply is unsupported and refuses the import
before saving. Import without --apply remains local.
Apply reads the snapshot using the existing connection’s data credential. It needs no admin token and uploads no profile. If the hub is too old to support the snapshot, the response is invalid, or no Desktop models are available, apply fails without substituting a local catalog or loopback origin. Upgrade/configure the hub and apply again.
This alias change does not fix the separate thinking / redacted_thinking replay and prompt-cache
request in #3719. Proxy admission alone does not enable native Anthropic passthrough; translated
Anthropic routes can still use prompt caching. Replay fidelity and cache-hit comparisons remain
separate work.
Key rotation, recovery and disconnect
Section titled “Key rotation, recovery and disconnect”Key rotation and recovery update the credential stored in the connection-owned Desktop profile
alongside the local connection credential. No manual Desktop reapply is required just to migrate
the key. Existing model IDs, family/default choices and the user’s current profile selection are
preserved; rotation does not select the managed profile again or re-enable a disabled integration.
CLI JSON rotation: "committed" means the new key is active. rotation: "rolled_back" means the
previous key was retained or restored, not that a new key was committed or the previous key revoked.
Uncertain or incomplete recovery is reported as such, rather than as successful rotation.
The first connected apply records the prior managed settings and selection for restoration.
Repeated apply and key rotation retain that original baseline. ocx disconnect restores the
connection-owned settings while preserving current user-added fields and unrelated profiles.
The previous selection is restored only if the managed profile is still selected; a later valid
user selection stays selected. A newly created profile with user additions is retained in readable
standard mode instead of deleting those additions. --keep-catalog keeps the catalog, not the
Desktop connection credential.
For an older managed profile without an original record, OpenCodex can migrate it when it unambiguously belongs to the current hub and a recognized connection key. Apply, rotation/recovery or direct disconnect can handle this case without a new flag or prerequisite reapply. A warning explains that disconnect will use standard mode because the previous settings were not recorded. That fallback removes only the connection-owned gateway settings, preserves user fields and a separate valid selection, and is reported as standard fallback, not original restoration.
Conflicting managed fields, unrecognized credentials or damaged restoration records are preserved and reported for resolution. Interrupted cleanup can resume for the same connection; it does not clear a newer connection or claim completion while restoration remains incomplete. Finish pending rotation recovery before starting disconnect, and retain the same catalog choice when retrying it.
Fully quit and reopen Claude Desktop after apply, rotation/recovery or restoration: changing files does not replace a credential already held by the running app. OpenCodex does not kill/restart the app automatically. Disconnect works locally without automatically revoking the hub key or erasing arbitrary external copies; revoke separately on the hub if desired.
The /model picker (“From gateway”)
Section titled “The /model picker (“From gateway”)”Claude Code 2.1.129+ discovers gateway models via GET /v1/models?limit=1000 and lists them in
the native /model picker. A row without a description reads “From gateway”; opencodex sends one
for every Claude Code CLI row (Routed by OpenCodex to <provider>/<model>; native rows use Routed by OpenCodex to native <model>, Fast rows append · Fast, and 1M rows keep the base description), which Claude Code
2.1.257+ shows in its place. Claude Code 2.1.278 accepts a picker id that contains claude or anthropic. An unrecognized id that starts with claude- is accounted at 200k unless compact is disabled, so opencodex exposes routed models as stable, reversible aliases that contain claude but do not start with claude-:
| Surface | Format | Example |
|---|---|---|
| Claude Code CLI | ocx-claude-<provider>--<model> (plain) or ocx-claude2-… (escaped) |
ocx-claude-native--gpt-5.6-sol |
| Claude Desktop 3P | claude-fable-5-p<code> (3-char base36 profile slot) |
claude-fable-5-p01q |
The proxy picks the family per request: ?ids=cli or ?ids=desktop wins; otherwise the
claude-code/* user-agent gets the readable CLI form and other clients get the Desktop code.
Both families decode forever — a model saved in settings.json under either form keeps working.
Each entry carries an honest display name such as gemini-3-pro (gemini), plus full model
capabilities (reasoning-effort ladder, thinking types) in the official ModelInfo shape so Claude
Desktop’s third-party gateway mode can offer its effort selector. Real Anthropic models keep their
canonical ids. The synthetic 2026 date is an internal slot, not a release date. Legacy hash aliases and claude-ocx-<provider>--<model> / claude-ocx2-<provider>--<model> ids from older configs still resolve. A legacy id configured in an OpenCodex model slot (claudeCode.model, tierModels, smallFastModel) is sent to Claude Code in its current spelling automatically. A legacy id saved by Claude Code’s own picker still routes, but Claude Code keeps its 200k accounting for that id. Pick ocx-claude- for a saved claude-ocx- id, and ocx-claude2- for a saved escaped claude-ocx2- id, so the effective catalog context window and compact both apply.
Older managed profiles exposed their internal YYYYMMDD slots directly. Affected Desktop builds strip
that terminal date when comparing active-session models, so two distinct routes can look identical
and no set_model control request is sent (#3782).
Current profiles expose non-date codes instead. After upgrading, reapply the Desktop profile and fully
quit and reopen Desktop; an already-running worker can still retain its old date-shaped id. The proxy
continues decoding those old ids for migration, but it cannot make Desktop emit a suppressed switch.
You can also try selecting the intended default model in the OpenCodex Claude Desktop profile, reapplying the profile, and starting a new conversation. This is a troubleshooting step, not a guaranteed fix. OpenCodex cannot observe picker state; it routes the model id carried by each request. Confirm what the client sends under Logs → requestedModel.
Models with a long context window (1M, or at least the 829,800-token default compaction threshold;
Claude models on an Anthropic route need a genuine 1M) get an extra …[1m] picker row: selecting it makes
Claude Code account a full 1M context for that model (auto-compaction stays on) — the proxy strips
the marker before routing.
Selecting one persists it to Claude Code’s settings.json model field; inbound requests resolve
the alias back to the routed model. On older Claude Code versions the picker stays native — set
slots via
ANTHROPIC_MODEL or type any routed id with /model (Claude Code passes strings through).
Alias grammar rules: provider must not contain / or -- or equal native.
Plain model ids (no / or ~) keep the v1 prefix ocx-claude-…. Model ids that contain / or
~ mint the v2 prefix ocx-claude2-… with escapes (/ → ~s, ~ → ~t), e.g.
openrouter/anthropic/claude-opus-4-8 → ocx-claude2-openrouter--anthropic~sclaude-opus-4-8.
v1 aliases decode literally (so a historical model id that contained the two-char sequences
~s / ~t is preserved); v2 aliases expand the escapes. Routes that the readable form cannot
express fall back to the hashed alias. Model ids MAY contain -- (resolution splits on the first
-- only); native slugs containing -- fall back to the hashed form.
Model resolution order: [1m] marker stripped → readable alias decoded → Desktop hashed
alias decoded → modelMap exact match → date-stripped match (-20250514 removed) → passthrough.
An unresolved date-shaped Desktop ID can also be a genuine native model missing from discovery.
Messages and count-tokens return HTTP 503 with the fixed desktop_model_mapping_unavailable error when the available
evidence cannot resolve that ID; this does not establish that the model is invalid. Unknown legacy
hash aliases still return HTTP 400. Neither case strips the date or falls back to another route.
Known IDs, registered mappings and exact modelMap matches keep their existing behavior, including
recognized real native IDs. Refresh model discovery or reapply the connected hub profile before
trying again; retrying alone does not guarantee resolution.
Each entry carries a display name like gemini-3-pro (gemini), plus full model capabilities
(reasoning-effort ladder, thinking types) in the official ModelInfo shape. Real Anthropic models
keep their canonical ids on both surfaces.
Context-variant [1m] marker
Section titled “Context-variant [1m] marker”These checks use the effective catalog window, which can include a custom-provider estimate. An estimate does not verify the relay’s actual capacity.
Models with an effective catalog context window of 1M, or at least the 829,800-token default
compaction threshold, get an extra …[1m] picker row; the floor is fixed, so a custom compaction
value does not lower it, and Claude models on an Anthropic route need a genuine 1M. Turning
auto-context off limits the row to 1M windows. Selecting it makes Claude Code
account a full 1M context. The proxy strips the case-insensitive [1m] suffix before alias
resolution and routing.
Auto context (big-context models without the 200k ceiling)
Section titled “Auto context (big-context models without the 200k ceiling)”Claude Code accounts 200k tokens for any model it does not recognize. Auto context (on by default) fixes that:
- Launch env slots get the
[1m]marker when the model’s effective catalog window is above 200k and at least the configured auto-compact threshold. Discovery and Desktop picker rows do not follow that threshold: they use the fixed 829,800-token floor (a genuine 1M for Claude models on an Anthropic route). CLAUDE_CODE_AUTO_COMPACT_WINDOW(default829800, range100000–1000000) is injected so the conversation auto-summarizes at that point.
Three config states:
- absent /
true: enabled (default) false: disabled — no markers, no compaction window injection- legacy
maxContextTokensset: auto-context is implicitly disabled
The compaction value is adjustable on the Claude page. Warning: raising it past a model’s real window breaks that model — the chat errors out before the summary can fire.
A model’s advertised context window does not guarantee that a tool-heavy request fits the
upstream input limit. A classified input-limit rejection reaches Claude Code as
invalid_request_error with context_length_exceeded, worded the way Anthropic words it
(prompt is too long: …), so Claude Code
compacts the conversation and retries on its own; a non-streaming response uses HTTP 400 instead
of a retryable 502. Providers on the native Messages lane get the same wording.
Overflow normalization uses only the first 65,536 UTF-16 code units of the provider’s error
message, including messages that already use Anthropic’s wording. This is a string-length limit,
not a byte or token limit. Text beyond it is discarded without an ellipsis or truncation marker,
so diagnostic details and token counts near the end may be absent from the error Claude Code
receives. The added prompt is too long: prefix, followed by a space, can make the final message
longer than this retained portion.
Within that portion, recognized Anthropic wording is kept; otherwise OpenCodex emits
prompt is too long: <requested> tokens > <limit> maximum only when it finds
maximum context length is <limit> tokens followed by resulted in <requested> tokens or
you requested <requested> tokens. It uses the first stated limit and the first matching request
count after it. If that pair is missing, reversed, or cut off, it prefixes the retained text with
prompt is too long: followed by a space, without inventing counts. A throughput limit that only mentions tokens
(for example “too many tokens per minute”) keeps its text and is not treated as an overflow.
If compaction itself still exceeds the limit, preserve the original
history and try compacting a fork with fewer enabled tool or MCP schemas, if your client supports
that workflow. A [1m] marker or larger client accounting setting does not raise the upstream
limit, and OpenCodex does not silently remove history or tools to make it fit.
Sub-1M native Anthropic models are never auto-marked. A compact window you export yourself wins for launch-slot marking (the proxy uses YOUR value to decide which models are safe to mark). Discovery rows ignore that export and keep the fixed floor. Invalid hand-edited config values fall back to 829,800.
Context accounting (1M by default, 200k opt-in)
Section titled “Context accounting (1M by default, 200k opt-in)”claudeCode.contextAccounting decides what opencodex picks by default. Absent (1m, the
default), long-window models are offered at 1M in the launch env slots, the Desktop pickers,
Desktop 3P (prefer1m) and generated subagents. Set 200k to opt out: nothing is marked [1m]
automatically, CLAUDE_CODE_AUTO_COMPACT_WINDOW is not injected, and Desktop 3P drops prefer1m
while keeping supports1m. A selector you mark [1m] yourself stays available (generated and
forced subagents still drop a marker the model’s window cannot carry), and discovery still lists
the · 1M rows of models with catalog windows of at least 1M. 200k wins over auto-context: a
compact window you export no longer turns automatic marking on, though opencodex leaves your own
export in place.
ocx claude config set --context-accounting 200kEffective model environment
Section titled “Effective model environment”effectiveModelEnv computes six slots injected by ocx claude / system env / shell file:
ANTHROPIC_MODEL, four ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU,FABLE}_MODEL, and legacy
ANTHROPIC_SMALL_FAST_MODEL. The effective Haiku is tierModels.haiku ?? smallFastModel, fed
to both Haiku variables.
When ocx claude launches in subscription mode, Claude Code’s own login carries a bare Claude id
such as claude-sonnet-5 straight to Anthropic, so those ids take their context windows from the
provider registry, whatever another provider lists under the same id. An unset Opus, Sonnet or Fable
slot then gets the native id Claude Code resolves that alias to, with the [1m] marker, because
behind a gateway Claude Code accounts an unmarked id at 200k. An anthropic row capped below 1M or
a claudeCode.modelMap entry keeps its id unmarked, and Haiku is never filled or marked. When the
launch uses proxy auth or nativePassthrough is off, the router decides, and only a routed row’s
window counts. System env and the shell file leave unset slots empty, since their values also reach
launches that go through a hub.
When both tierModels.haiku and smallFastModel are absent, OpenCodex leaves both helper variables unset; Claude Code then chooses its native helper model (currently Sonnet), which may incur native-provider charges.
Roster agents (injectAgents)
Section titled “Roster agents (injectAgents)”Proxy startup/ensure, ocx claude, and relevant dashboard saves sync your featured subagent roster
(Subagents tab, up to 5 models) plus ocx-self into ~/.claude/agents/ocx-*.md.
ocx-selfpins your/modelpicker default (falling back toclaudeCode.model); omitted when neither exists. It does NOT use model inheritance.- Each agent body contains an
<!-- ocx-route: <model> -->directive — the proxy uses this to pin the real route. The Agent tool’smodelargument is therefore inert; pass"haiku"as a placeholder. - Frontmatter carries the alias; routing is directive-driven.
- Only marker-verified
ocx-*.mdfiles containinggenerated-by: opencodexare ever overwritten or pruned; your own agents are never touched. - Files are atomically synced per file (write + rename).
enabled: falseorinjectAgents: falseprunes all verified-owned definitions.- Successful CLI first-party saves and persisted Desktop first-party setup also attempt best-effort roster sync, including repeated setup to repair missing definitions. Refused setup and rollback do not sync agents. Turning first-party off alone does not disable agent registration.
- GUI PUT and roster changes resync immediately; every foreground or background proxy start/ensure reconciles the owned files before a later Claude Code launch reads them. Start a new Claude session after registration so it loads the generated definitions.
Dispatch: subagent_type: "ocx-gpt-5-6-sol". 1M-capable targets carry [1m] automatically.
Bundled-skill elision (blockedSkills)
Section titled “Bundled-skill elision (blockedSkills)”Claude Code’s bundled claude-api skill injects ~840KB (~136k tokens) of Anthropic documentation
that auto-triggers on Claude model mentions. Routed models are not trained on that bundle, so by
default opencodex replaces the skill’s content with a short stub on routed requests. Native
Anthropic passthrough is untouched.
Two carriers are handled:
- Tool-result carrier: assistant
Skill(...)calls — the pairedtool_resultbody is replaced by a stub when the lowercased JSON input contains a blocked name. - Text-block carrier: a user text block ≥10,000 characters starting with
Base directory for this skill:— matched when the directory basename equals a blocked name (case-insensitive). The directory line is inspected only up to 4,096 UTF-16 code units; a longer line is sent unchanged, including when it has no terminating newline.
Configure with claudeCode.blockedSkills (default ["claude-api"]; [] disables elision
entirely). The stub keeps tool call/result pairing intact.
Model map (interception)
Section titled “Model map (interception)”claudeCode.modelMap rewrites inbound Anthropic model ids before routing:
{ "claudeCode": { "modelMap": { "claude-sonnet-4-5": "gemini/gemini-3-pro", "claude-haiku-4-5": "gemini/gemini-3-flash" } }}Lookup order: discovery alias → exact id → id with date suffix stripped (-20250514) → passthrough.
See Desktop alias resolution for the rejection policy.
Sidecar matrix: web search and image understanding
Section titled “Sidecar matrix: web search and image understanding”Routed models do not all have the same hosted tools or image support. opencodex fills those gaps before the main model answers:
- The web-search sidecar runs the real hosted search, then gives the routed model the answer and sources as a tool result.
- The vision sidecar describes an attached image before calling a model listed in
noVisionModels, then replaces the image with that description.
Both sidecars can use either backend:
| Backend | How it runs | What it requires |
|---|---|---|
openai |
A small GPT model through the ChatGPT forward provider |
A ChatGPT login and an enabled authMode: "forward" provider |
anthropic |
Claude through stored Anthropic OAuth; web search uses web_search_20250305 and vision sends the image to Claude for description |
An enabled adapter: "anthropic", authMode: "oauth" provider whose active stored account is not marked needsReauth |
An explicit backend always wins. When it is omitted, the web-search sidecar always selects
openai (anthropic runs only when explicitly configured), while the vision sidecar selects
anthropic if a usable stored Anthropic OAuth account exists, otherwise openai. Explicitly selecting
anthropic without a usable credential fails closed: opencodex does not silently borrow
ChatGPT credentials or switch backends. The OpenAI backend likewise stays off without both login
auth and a forward provider.
Claude-inbound routed replays attach the main ChatGPT login to the internal request, so OpenAI sidecars remain reachable even though Claude Code’s inbound bearer is only the proxy credential. That bearer is never forwarded to the routed main provider.
{ "webSearchSidecar": { "backend": "anthropic", "model": "claude-sonnet-5", "maxSearchesPerTurn": 3 }, "visionSidecar": { "backend": "anthropic", "model": "claude-sonnet-5", "maxDescriptionsPerTurn": 8 }}maxDescriptionsPerTurn limits new image descriptions in one main-model turn. Cache hits and
duplicate in-flight descriptions do not consume the cap. Successful descriptions for data:
images are cached by backend, model, detail, image bytes, and request context, so the same
image-and-context pair is not described again on every replay. Remote https: images are never
cached because their contents can change.
See the configuration reference for every key. Anthropic-OAuth web search and image description reuse the repository’s existing Claude Code OAuth fingerprint precedent, but should still be soak-tested with your account and workload before you depend on them for long unattended runs.
Client tools on Cursor routes
Section titled “Client tools on Cursor routes”When Claude Code uses a Cursor-backed model, OpenCodex preserves recognized client tool names
such as Bash, Read, Write, Edit, Grep, Glob, and Task in the Cursor catalog, tool
guidance, and replayed calls. This keeps the names consistent with Claude Code’s instructions.
Denied Cursor-native execution frames direct the model to the client tools available that turn.
A mixed catalog containing a bare Codex exec_command or shell_command bridge uses wire
aliases such as ocx_client_Read to avoid Cursor tool collisions; calls returned to Claude Code
still use its original tool names. Namespaced MCP tools keep their namespaced identities.
OpenCodex includes these aliases in Cursor’s transport byte budget, so a tool whose definition
would exceed the limit is omitted for that turn.
Reasoning effort
Section titled “Reasoning effort”On translated Messages → Responses requests, Claude Code’s /effort setting maps as follows:
| Wire format | Mapping |
|---|---|
thinking.type: "adaptive" + output_config.effort |
Effort passed directly (minimal|low|medium|high|xhigh|max|ultra) |
thinking.type: "enabled" + budget_tokens |
≤4096→low, ≤16384→medium, above→high |
thinking.type: "disabled" |
reasoning: { effort: "none" }; summary omitted |
For translated requests, the resolved tier appears in the request log’s Reasoning effort column.
Managed native Messages log enabled thinking budgets as budget:<tokens> when no recognized
output_config.effort is present; this logging does not change the wire body.
Inbound translation (Messages → Responses)
Section titled “Inbound translation (Messages → Responses)”The proxy translates every Anthropic Messages API request into the Codex Responses API format:
| Messages input | Responses output |
|---|---|
Top-level system |
instructions (text blocks joined with \n\n) |
messages[].role: "system" |
Also folded into instructions |
| User text / image | input_text / input_image (base64 → data URL) |
| Assistant text | output_text |
Assistant tool_use |
function_call (input → JSON-stringified arguments) |
User tool_result |
function_call_output (is_error → [tool error] prefix) |
tool_reference in a tool result |
Text Tool loaded: <tool_name> in the paired result; preserves loaded-tool names without adding declarations or enabling translated server-side deferral |
thinking / redacted_thinking replay |
reasoning items with bounded ocxr1 envelopes for signatures and redacted payloads |
| Function tools | {type: "function"} (web_search* → {type: "web_search"}). On a route to api.anthropic.com, the typed web_search_* and web_fetch_* declarations are also sent to Anthropic as declared and run server-side, unless the web-search sidecar supplies web_search itself. A search or fetch with a matching call and complete input comes back to the client as its server_tool_use block and its web_search_tool_result or web_fetch_tool_result, verbatim. Orphan results are dropped without synthesizing a call, while the surrounding text is preserved. When the client sends a complete pair back in its history, the next api.anthropic.com request carries it in place only if the following block uniquely identifies its original turn. Repeated anchors, unmatched or duplicate call/result ids, malformed call input, or a result appearing before its call drop the exchange. Call input must be an object with a nonempty search query or fetch URL string; other destinations never see the exchange |
tool_choice |
auto→auto, none→none, any→required, named function→{type:"function",name}, hosted WebSearch/web_search→{type:"web_search"} |
max_tokens |
max_output_tokens |
stop_sequences |
stop |
Claude Code auto-mode always sends stop_sequences. For models in the routed provider’s noStopModels list, OpenCodex omits stop on both the Chat Completions and Responses wires, so xAI reasoning models such as grok-4.7 and grok-4.6 do not return 400 invalid-argument and get marked temporarily unavailable. See noStopModels.
Replay preserves non-hidden signed blocks (including empty thinking) and opaque redacted blocks on the intended Anthropic adapter. hideThinkingSummary remains unchanged: locally hidden signed text is not exposed to Claude clients, and lossless replay through that hidden Claude boundary is not established. Older combined reasoning envelopes cannot recover original block order once streaming text has been emitted. claudeCode.compatibility: "enforce" still rejects thinking replay. This does not establish live Anthropic acceptance or cache-hit improvements; #3719 remains open.
Error cases (400): malformed JSON; missing/empty model; missing/empty messages; unsupported
role; tool_result without tool_use_id; tool_use without id/name; named tool_choice without
name.
Unicode-property patterns in tool schemas
Section titled “Unicode-property patterns in tool schemas”A JSON Schema pattern written for JavaScript may use Unicode property escapes such as
\p{Cc} or \P{L}. OpenAI-family backends validate pattern by compiling it with Python’s
re, which does not support those escapes, and a schema they cannot compile is refused whole —
so a single such pattern on one built-in tool fails every request in the session, not just calls
to that tool.
To keep ordinary Artifact parameters working, the openai-chat and openai-responses adapter
paths omit scalar pattern constraints containing Unicode property escapes in ordinary positive
schema positions. Sibling constraints, required, literal data and supported regexes remain.
A tool implementation must validate its own inputs because an omitted constraint is not enforced
by this proxy.
patternProperties matchers and their value schemas remain unchanged. Removing a matcher can
change which keys are evaluated by an ancestor’s unevaluatedProperties, so local openness is
not enough to prove a safe transformation. Patterns under not, oneOf, if, contains,
$defs and definitions also remain unchanged: relaxing those subtrees can change negation,
branch selection, match counts or the meaning of a reference.
The destination validates these preserved schemas. An ECMA-compatible destination can use the original regex; a destination that cannot compile it may reject the schema. OpenCodex does not silently replace that contract with one that forbids previously valid arguments.
This is normalization on the selected adapter path, not a provider-wide guarantee. Provider configuration and authentication are untouched, and a provider on a different adapter is unaffected.
It is a compatibility measure, not a claim that every custom OpenAI-compatible backend rejects these patterns. What it costs is worth knowing: an omitted regex is not preserved anywhere and is not enforced upstream, so a tool implementation should validate its own inputs rather than relying on the schema to reject a malformed argument.
Outbound translation (Responses → Messages SSE)
Section titled “Outbound translation (Responses → Messages SSE)”| Responses event | Messages SSE |
|---|---|
response.created |
message_start + ping |
| Heartbeat | ping |
| Text deltas | content_block_start → content_block_delta (text) → content_block_stop |
| Reasoning summary/text | thinking block with the replayed signature, or a bounded ocxr1 fallback envelope |
| Redacted reasoning | redacted_thinking blocks replayed from the reasoning envelope |
| Function-call frames | tool_use block with input_json_delta |
| Terminal event | message_delta → message_stop |
| EOF before terminal | 502-style api_error |
Stop reason mapping: completed → tool_use (if any tool call) or end_turn;
incomplete/max_output_tokens → max_tokens; incomplete/content_filter → refusal.
Error taxonomy: 400 invalid_request_error, 401 authentication_error,
402 billing_error, 403 permission_error, 404 not_found_error, 409 conflict_error,
413 request_too_large, 429 rate_limit_error, 504 timeout_error, 529 overloaded_error,
other 5xx api_error. Retry-After is preserved.
Prompt caching and token usage
Section titled “Prompt caching and token usage”Anthropic-routed requests: the adapter manages cache breakpoints for tools, system content,
and the penultimate user message, plus top-level automatic cache_control. Stable turns normally
produce about a 99.9% cache hit rate.
Native OpenAI/ChatGPT routing: derives a session-scoped prompt_cache_key (from
metadata.user_id when present, falling back to a system-content hash) and session_id header
for cache affinity. The cache key includes model and full tool schemas.
Token math: Anthropic output subtracts cached_tokens and cache_write_tokens from
input_tokens, exposing them as cache_read_input_tokens and cache_creation_input_tokens.
Request logs map those back to inclusive inputTokens, with reads in both cachedInputTokens and
cacheReadInputTokens, writes in cacheCreationInputTokens. The Usage page reports cache hits
and cache creation separately.
count_tokens: routed models use an approximation (serialized system + messages + tools).
Native Anthropic models with an sk-ant- credential pass the request through to the real
Anthropic /v1/messages/count_tokens endpoint.
Debug capture
Section titled “Debug capture”ocx debug claude on|off|status|reset, OCX_CLAUDE_DEBUG=1, or PUT /api/debug {"claude": true}
controls inbound capture. GET /api/claude/inbound-debug returns {enabled, entries} (newest
first, ring of 20).
Each entry records: at, endpoint, model, resolvedModel, stream, maxTokens,
thinkingType, thinkingBudgetTokens, outputConfigEffort, metadataKeys,
hasMetadataUserId, hasSystem, raw anthropicBeta, and eight-character HMAC equality tags for
user id / system. No prompt text, raw object, or stable cross-run hash is stored. Disabling
Claude debug immediately clears the ring.
GUI (Claude page)
Section titled “GUI (Claude page)”Open Connect → Claude for the one-page Claude Code settings. The page shows these controls and sections in order:
- Claude Code CLI first-party switch
- Get started with
ocx claudeand the manual environment block - General with compatibility, agent instructions, Fast Mode, and context controls
- Background helper model selector
- Model interception editor
- Available models, the live preview of Claude Code’s
/modelpicker aliases - Claude connection switch, also available on the Claude card in the Connect overview
The sticky Save bar shows No changes when the saved settings match the page, or Unsaved changes after you edit them. Revert discards unsaved edits; Save commits the editable settings. The Claude connection and Claude Code CLI first-party switches apply immediately. Save never changes either switch.
Claude Desktop is a separate tab under Connect. Its Connection mode selector offers gateway (default) or first-party, with the running proxy port in first-party mode. Only Save & apply switches modes; Save alone stores the gateway profile lanes for a later gateway apply and leaves the current mode as is.
GET /api/claude-code returns effective defaults, config, context-window registry, effective env,
available route ids, aliases, and port. PUT /api/claude-code is partial and preserves omitted
fields; null resets context/blocklist/compact-window values.
Troubleshooting
Section titled “Troubleshooting”Claude Code says “Did 0 searches” — Current builds translate completed Responses
web_search_call items into paired Anthropic server_tool_use and web_search_tool_result blocks,
including usage.server_tool_use.web_search_requests. Update opencodex if an older build completed
the search but Claude Code still counted zero.
A sidecar does not activate — For backend: "openai", confirm you are logged into ChatGPT and
have an enabled authMode: "forward" provider. For backend: "anthropic", confirm the active stored
Anthropic OAuth account is not marked needsReauth. An explicit Anthropic selection without that
credential intentionally fails closed.
“claude.ai connectors are disabled” — An ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN is set
in your shell. ocx claude deliberately does NOT set ANTHROPIC_API_KEY; if you have it exported,
unset it. ocx claude injects ANTHROPIC_BASE_URL, discovery, auto-context, and configured model slots — but never ANTHROPIC_API_KEY.
Models not showing in /model picker — Verify CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 is
set (automatic with ocx claude). Run ocx claude to refresh the gateway model cache at
~/.claude/cache/gateway-models.json. Check claudeCode.enabled is not false.
Stale environment after port change — If the proxy port changed, old shells may have a stale
ANTHROPIC_BASE_URL. Open a new terminal, or re-run ocx claude.
200k context ceiling despite big model — Select the [1m] variant in the picker, or enable
auto-context (on by default). If the picker shows no [1m] row, the model’s effective catalog context
window may be below the fixed 829,800-token floor.
High token count from skill loads — The bundled claude-api skill (~136k tokens) auto-loads
on Claude model mentions. This is normal for native passthrough; on routed models, opencodex stubs
it by default (blockedSkills: ["claude-api"]).
MCP tool schemas fill the context on turn one — Claude Code turns MCP tool deferral off
whenever ANTHROPIC_BASE_URL is not a first-party Anthropic host. The check is keyed on the
host, not the model, so it applies to every ocx claude session: with many connectors
configured, every tool schema is inlined before you type anything. Re-enable deferral with:
{ "claudeCode": { "toolSearch": true } }This is off by default because it only pays off on native Anthropic passthrough routes.
Deferral is a server-side optimisation, not a smaller request: Claude Code still sends every
tool definition, and Anthropic’s API is what keeps deferred schemas out of the model’s context
and answers the tool_search server tool with tool_reference blocks. A translated (routed)
model never reaches that machinery — opencodex drops the tool_search tool and ignores
defer_loading — so the provider still receives every schema while Claude Code stops counting
them and therefore stops compacting. With claudeCode.compatibility: "enforce" the same
request is rejected with 400 instead. Turn it on when ocx claude is routed to an Anthropic
model; leave it off for third-party routes until translated deferral is supported.
Accepted values follow Claude Code’s own parser: true, "auto", "auto:N" (N at least 100)
and "force". A value you export yourself always wins over the injected one.
Subagent dispatches to wrong model — Roster agents (ocx-*) use <!-- ocx-route: ... -->
directives, not the Agent tool’s model argument. Make sure the directive matches the intended
route. Pass "haiku" as the model placeholder.
Set claudeCode.stabilizePromptCache to true in config.json to peel supported trailing Claude harness notices from system instructions on translated routes. Recognized <total_tokens>N tokens left</total_tokens> footers are dropped, including repeated footers. TaskCreate reminders still move to a trailing user message; if only token footers were peeled, no input message is added. The default is false. Enable it only when this role change is appropriate for your clients. It preserves fenced examples and unmatched text; native Anthropic passthrough is unchanged. The metadata-less prompt-cache key then follows stabilized instructions. This does not create conversation identity or guarantee upstream cache hits.
On every translated Chat route, timeline reminders keep their position in the
conversation, after any pending tool results. This prevents a newly appended
reminder from rewriting the leading system prompt, and stops a mid-conversation
instruction from arriving ahead of the turns it was written to follow. The role
that slot carries is decided separately: a reminder is sent as system unless
the provider records foldDeveloperRoleToSystem: false, which states that the
upstream accepts the developer role and forwards it in the same position. An
upstream that does not accept it answers 400 role 'developer' is not allowed
and the turn never starts, so an unrecorded destination folds. This applies with or without
stabilizePromptCache, and native Anthropic passthrough is unchanged. Cache
reuse still requires stable session identity and upstream cache availability.
Changes to earlier instructions or tools, and conversation compaction, can still
affect cache hits; preserving reminder order alone does not guarantee reuse.
Intercept token recovery
Section titled “Intercept token recovery”The running authenticated proxy validates the current token for each new CONNECT request. An explicit first-party apply recreates a missing token and refreshes the owned settings without requiring a proxy restart; existing tunnels are not revoked. Invalid, linked, oversized or non-token files are refused rather than overwritten. Inspect such an entry before removing only the confirmed obsolete token file and applying first-party mode again; never delete its link target.
First-party picker context markers
Section titled “First-party picker context markers”The Desktop Code-tab picker adds [1m] to routed models whose effective catalog context window is at least one million tokens or at least the 829,800-token default compaction threshold (for example a native GPT-6 model opted into its 872k window), so Claude uses its 1M accounting instead of the smaller custom-model fallback. Claude models on an Anthropic route still need a genuine 1M. Labels, profile order, and provider routes stay unchanged. Unknown and shorter windows remain unmarked. The picker cannot guarantee that a Desktop or remote runner receives the matching compaction environment, so a long-window model that outgrows its real window gets a prompt is too long error, which Claude Code answers by compacting. Desktop 3P supports1m/prefer1m and generated subagents follow the same rule. The paired auto-context setup for ocx claude is unchanged. An existing conversation keeps its saved selector until you select the model again from the refreshed picker.
Forced Claude Code subagent model
Section titled “Forced Claude Code subagent model”The Subagents page offers Force all subagents onto one model, off by default. Select an exposed roster-style id, such as combo/tev-auto, then enable the switch. The roster is offered first; unavailable saved roster entries cannot be force targets.
ocx agent subagents force combo/tev-auto sets claudeCode.subagentModelForce; ocx agent subagents force - clears it. ocx agent status reports the setting. GET /api/subagent-models returns force, forceAvailable, and forceStatus; PUT accepts { "force": "combo/tev-auto" } or { "force": null } without changing the roster. Omitting force leaves it unchanged. Invalid or unexposed targets are rejected on write; stale targets are reported and skipped at launch.
This takes effect on the next routed ocx claude launch, injecting CLAUDE_CODE_SUBAGENT_MODEL as an explicit proxy alias (with [1m] when the effective catalog window reaches the fixed 829,800-token floor, and only for a genuine 1M window on Anthropic Claude models and bare claude-* selectors; native Claude targets use a reversible native alias) and CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1. Each nonempty shell-exported variable independently wins. Native launches inject neither variable; plain claude is not affected. No plugin files or settings.json are modified by this setting.
Claude Code 2.1.257 or newer is required for FORCE. Plugin and built-in agents (including Explore/Plan) and per-call model arguments are overridden. Forks and subagent skills with model: inherit keep the main conversation model. The main loop and Haiku/small-fast sidecars are unaffected. Existing roster files remain available.
The dashboard warns about old or unknown CLI versions, unavailable targets, and either variable already present in settings.json → env (which overrides launch env). Detection is read-only and server-local: it cannot inspect another launch shell, another machine, or project-local settings. An unknown result is not proof of force support.
Explicit gateway selectors on a generated agent request take precedence over its legacy ocx-route fallback, even if the saved force setting changes after launch. For shell or settings overrides of generated roster agents, use an explicit gateway alias; bare Claude ids retain the older-client fallback behavior. Native aliases restore their bare model before the existing credential and model-map checks. Connected launches validate force targets against a fresh authenticated gateway catalog; failed discovery skips automatic force injection, and cached context windows alone never prove availability.

