--- title: CLI Providers, Accounts, and Models description: Provider configuration, credentials, quota, and model catalog commands. --- These commands configure upstream providers, authenticate accounts, manage credential pools, and control the model catalog exposed to Codex. Leftover-argument errors from `account list`, `account current`, native main profiles, and provider commands redact values of credential options, including `--code`, `--token`, `--api-key`, `--key`, `--secret`, `--password`, and `--admin-token`, in both `--option value` and `--option=value` forms. Local provider registration uses `ocx provider add --api-key `; an inline `--api-key=` is rejected with its value redacted. `ocx provider edit` does not accept `--api-key`. ## Providers ### `ocx provider ` Non-interactive provider management. Registry entries are seeded by name; a custom name requires both `--adapter` and `--base-url`. | Subcommand | Supported flags | Action | | --- | --- | --- | | `list` | `--json`, `--jsonl` | List configured providers and the remaining registry entries; `--jsonl` emits one configured provider object per line. | | `add ` | `--adapter `, `--base-url `, `--api-key `, `--responses-path `, `--auth-mode `, `--default-model `, `--set-default`, `--force`, `--json`, `--sync`, `--live` | Save locally by default; `--sync` attempts client sync in either output mode. `--live` writes through the running proxy and cannot combine with `--sync`. `--force` permits overwrite. | | `edit ` | provider field flags, `--headers `, `--model-context-tier `, `--upstream-http-version `, `--fast `, `--context-window `, `--json` | Edit validated live provider fields without replacing key pools. Repeat `--model-context-tier` for multiple Copilot models. `--headers` merges custom request headers; pass `{}` or `-` to clear them. | | `test ` | `--model `, `--yes`, `--json` | Check discovery; with `--model`, request one confirmed completion test. | | `show ` | `--json` | Show config with API keys masked. | | `remove ` | `--json`, `--live`, `--yes` | Local removal refuses the default and last provider. Live removal requires `--yes` and uses server dependency checks, default reassignment and account/custom-model cleanup. | | `set-default ` | `--json`, `--live` | Select a local default, or update the running proxy with `--live`. | | `pacing ` | `--json`, `--enabled `, `--rpm `, `--min-interval-ms `, `--max-concurrent `, `--file ` | Read configured rules and runtime status; update scalars or replace rules from a file. File and scalar flags are exclusive. | | `snapshot` | `--json` | Read a redacted, validated public provider-editor document from the running proxy. | | `apply` | `--baseline `, `--file `, `--yes`, `--json` | Apply a reviewed editor document with a baseline comparison; removals/renames require `--yes`. | | `selected ` | `--set `, `--clear`, `--json` | Read or update the provider model allowlist. | | `quota` | `--refresh`, `--json` | Read provider quota reports. | | `resets` | `--limit `, `--json` | List recently detected quota-window resets. | | `presets` | `--json` | List dashboard provider presets. | | `account-mode` | `pool`, `direct`, `--json` | Select pooled or direct Codex account routing. | | `import-cc-switch` | `--source `, `--provider `, `--apply`, `--restore`, `--allow-private-network`, `--json` | Preview, selectively apply, or restore keyless provider entries from a CC-Switch SQLite database (`cc-switch.db`) or legacy JSON file. | ```bash ocx provider list --json ocx provider list --jsonl # one configured provider object per line ocx provider test ark ocx provider add anthropic --set-default --sync ocx provider add local-dev --adapter openai-chat --base-url http://localhost:11434/v1 ocx provider show anthropic --json ocx provider edit github-copilot --model-context-tier gpt-5.6-luna=long_context ocx models --provider anthropic --json ocx models live --provider ark --json ``` `--jsonl` writes only configured providers, one JSON object per line, and omits the `registryCount` summary from `--json`. Each object has the same fields as an item in the `configured` array. Use it for scripts that process one configured provider object per line. `--json` and `--jsonl` cannot be combined. Each configured row also has `active`. It is `false` when this device is linked to a Home (a Remote Link Child or a Hub-mode client): the row is local configuration that the Home's providers shadow, so it does not serve requests while the link is connected. It is `true` when the row actually serves, including when the link state cannot be read. Rows never carry credential values: `baseUrl` drops embedded userinfo and masks credential-shaped query values such as `key` or `token`, while keeping ordinary parameters such as `api-version`. :::caution[Custom headers are not a credential channel] `--headers` is for non-secret request metadata — routing hints, tenant or project selectors, tracing ids. It is **not** a place to put authentication material, and the validator rejects the standard credential header names (`Authorization`, `X-Api-Key`, `Cookie`, and the rest) with a pointer to `apiKey` / `authMode`. The validator cannot recognize an arbitrary name such as `X-My-Token`, so the boundary is yours to respect. Two reasons it matters: - The JSON is a command-line argument, so a secret in it lands in shell history and in the process list, where any other process on the machine can read it before the CLI ever redacts anything. - Header values are persisted in `config.json` in cleartext, unlike API keys, which have their own storage and masking path. Use the supported credential login or stdin flow for secret entry. Do not place keys in an agent transcript or copy them into command examples. ::: ### Local saves and live provider state Start with offline discovery: `ocx provider --help`, then the leaf's `--help`. `list` and `show` read local configuration; `add`, `remove`, and `set-default` also use local configuration unless given `--live`. Other provider management operations below require a running proxy. Check `ocx ready --json` and `ocx status --json` before live work to confirm the intended target and version. A local list does not establish that the proxy adopted a saved change. Local `provider add --json` reports `needsSync: true`. Adding `--sync` actually attempts synchronization in both JSON and human output modes. Its receipt adds `sync: {status, ok}`; only `status: "applied"` with `ok: true` clears `needsSync`. No proxy (`not-running`), refused sync or failed sync returns nonzero while preserving the local save. Policy-skipped or catalog-only results may exit 0 with `needsSync: true`; they are not an applied client sync. Catalog ownership or saved-config safety refusals leave the catalog unchanged, return exit 1, and show recovery guidance in human output or `sync.warning` in JSON, even if config injection succeeded. For authorized live changes: ```bash ocx provider snapshot --json ocx provider add anthropic --live --json ocx provider set-default anthropic --live --json ocx provider snapshot --json ``` Live add uses the target's presets and refuses an observed existing provider without `--force`. The check and server upsert are not atomic: concurrent changes can race. Custom providers still need `--adapter` and `--base-url`. `--live` never falls back to a local save; combining it with `--sync` is invalid. Credentials belong in the supported human login or stdin flow, not CLI examples. To remove an authorized provider, use `ocx provider remove --live --yes --json`. The server checks dependencies and applies default reassignment and account/custom-model cleanup. Inspect the receipt and read back the target after a write. A live receipt can be `{"success":true,"name":"example","catalogRefresh":{"status":"committed","changed":true,"degraded":false,"notices":[]}}`. `success` means configuration persisted; it does not by itself prove catalog convergence. For example, `{"success":true,"name":"example","catalogRefresh":{"status":"skipped","reason":"busy","retryable":true}}` returns exit 1 even though the save succeeded. Failed refresh, or skips for `stale`, `refused` and `catalog-unavailable`, also return nonzero. Preserve degraded notices. Null, absent or `not-requested` refresh can exit 0 without a client sync. Read back before recovery; do not repeat a persisted write to repair convergence. Errors use safe prose on stderr, including in JSON mode, rather than a JSON error envelope. `provider test ` without `--model` checks upstream model discovery connectivity. A failed connection exits 1; success and a static catalog with no applicable discovery endpoint exit 0. `applicable: false` is an expected static-catalog result, not successful inference or a failed connection. To send one completion request for a chosen model, use: ```bash ocx provider test my-relay --model my-model ocx provider test my-relay --model my-model --yes --json ``` This test may incur provider charges. An interactive terminal asks for confirmation; `--yes` accepts the charge warning. Non-interactive use and `--json` require `--yes`. Using `--yes` without `--model` is a usage error. Declining confirmation sends no request and exits 1. The command uses the running proxy's management API. The completion test sends the fixed prompt `Reply exactly OK.`, requests a 32-token output cap, stops after 20 seconds or 64 KiB, and never retries or fails over. It supports key-auth or local OpenAI Chat and Responses providers, including per-model wire overrides. Disabled, OAuth, forwarded-auth and unsupported adapter or transport configurations return an applicability reason without sending a completion. TLS profiles, HTTP version pins, enabled request pacing and custom executors with a provider proxy override are currently unsupported. Results include time to first text delta (TTFT), total latency, the requested and wire model ids, sanitized upstream model metadata and a distinct match, mismatch or unknown comparison. Missing or conflicting metadata is unknown. The comparison uses the upstream id before masking, against the id actually sent after alias and suffix handling. Provider-reported metadata cannot verify model identity: `identityVerified` is always `false`. Generated text is not returned. A failed completion exits 1; success and a not-applicable result exit 0. ### Transport settings and request pacing `provider edit` is already live. Use `--upstream-http-version http1.1` to pin that protocol, or `--upstream-http-version -` to clear it. `--fast on|off` toggles Fast; `--context-window N` sets a positive provider override and `--context-window -` clears it. Omitted settings stay unchanged; zero is not a clear operation. ```bash ocx provider pacing anthropic --json ocx provider pacing anthropic --enabled on --rpm 30 --min-interval-ms 1000 --max-concurrent 2 --json ocx provider pacing anthropic --json ``` The read returns `{provider, rules, status}`. `rules: null` means unconfigured; `status` contains separate runtime queue/timing observations. Numeric flags alone do not enable an unconfigured block; `--enabled off` disables pacing. RPM accepts validated positive fractional values. Interval/concurrency require positive integers. Scalar edits preserve the observed model rules but PATCH replaces the whole block without CAS, so a concurrent edit can be overwritten. Use snapshot/apply below when baseline comparison is required. For a complete rules object, including model-specific rules, use `ocx provider pacing anthropic --file pacing.json --json`. A minimal rules file is `{"enabled":true,"requestsPerMinute":30}`. File mode replaces the entire block and cannot be combined with scalar flags. `--file -` reads piped stdin; the input limits below apply. ### Snapshot, edit and apply with a baseline Keep the same intended host and CLI context across these steps. Target pinning lasts within one invocation; snapshots contain no target identity token. ```bash ocx provider snapshot --json > providers.baseline.json cp providers.baseline.json providers.next.json ``` Edit the next file and review the diff while preserving the original baseline. Each document contains exactly `{defaultProvider, providers}`. Snapshot is a read-only redacted editor projection, not raw config export. Do not add secret, derived or unknown fields: credentials, any `headers` field and display markers (`hasApiKey`, `hasHeaders`, `xaiResponsesOptInState`, `initialModelSelection`) are not batch-editable. Raw configuration export may expose credentials and belongs in a human-operated terminal outside an agent session. ```bash ocx provider apply --baseline providers.baseline.json --file providers.next.json --json ocx provider snapshot --json ``` Add `--yes` only for reviewed, authorized removals or renames. Batch PUT preserves untouched private values and uses the public baseline to detect conflicts. It does **not** perform single-provider DELETE's OAuth account cleanup. HTTP 409 returns exit 5: take a fresh snapshot, review concurrent changes and rebuild the proposed edit. Do not replace the baseline or retry automatically. Unknown write outcomes require inspection, not a rollback assumption. Use regular UTF-8 JSON files or explicit piped `-`; at most one batch input can be stdin. Each input has a 4 MiB limit and a 30-second read deadline. The combined serialized `{baseline,next}` request must also fit 4 MiB. Interactive stdin, special files, conflicting flags, invalid fields and unconfirmed removals are refused before mutation with exit 2. Input errors never echo the submitted values. ### Keyless CC-Switch provider import ```bash ocx provider import-cc-switch --source ~/.cc-switch/cc-switch.db ocx stop ocx provider import-cc-switch --source ~/.cc-switch/cc-switch.db --provider my-provider --apply # Start the proxy in another terminal (or run ocx ensure), then add credentials: ocx start printf '%s\n' "$API_KEY" | ocx account add-key my-provider # Separate rollback path (stop the proxy first; removes unedited keyless entries only): ocx stop ocx provider import-cc-switch --restore ``` `ocx provider import-cc-switch` projects values **only** from an explicit allowlist of non-secret metadata fields (provider IDs, app types, base URLs, wire dialects, and default models) from a plain regular CC-Switch SQLite database or legacy JSON file. Source and embedded-config bytes are read transiently to scan the input, while secret fields, free-text display names, and TOML comments are skipped at the byte level, never decoded into strings, written, or logged, and transient byte buffers are zeroed in `finally`. Narrow base-URL path rules (`http(s)://host[:port]` with an empty path or 1–4 lowercase `[a-z0-9._-]` API path segments such as `/v1`, `/api/v1`, or `/compatible-mode/v1`) and identifier/model syntax checks act as defense in depth and reject non-conforming values without echoing them. Source files are opened once with `O_NOFOLLOW | O_NONBLOCK` descriptor verification and deserialized read-only in memory for SQLite without temporary files; SQLite files with multiple hard links (`nlink > 1`) are refused so sidecar checks cannot be bypassed via an alias path, and if a sibling `-wal` or `-journal` file is non-empty, import refuses and asks you to close CC-Switch (or checkpoint the database) and retry. Only import from a trusted local CC-Switch source, and verify every printed ID, model, and `baseUrl` before adding credentials: - **Preview (default):** requires `--source `, writes nothing, and reports discovered, eligible, conflicting, and invalid entries. - **Apply (`--apply`):** requires `--source ` and a stopped proxy (`ocx stop`), and is refused on a Remote Link Child. Repeat `--provider ` to import only selected entries, or pass `--allow-private-network` to permit loopback or private-network base URLs. Even without a prior preview, `--apply` prints every imported provider ID, default model, and destination URL before writing keyless provider entries and recording a rollback journal at `$OPENCODEX_HOME/cc-switch-provider-import-journal.json` (`stdout` in text mode, `stderr` in `--json` mode); a second apply is refused while an active journal exists. Verify the printed entries, start the proxy (`ocx start` in another terminal or `ocx ensure`), and add credentials with `printf '%s\n' "$API_KEY" | ocx account add-key ` (piped stdin) or `ocx login `. - **Restore (`--restore`):** separate rollback path (not a continuation of adding credentials) that requires a stopped proxy (`ocx stop`), is refused on a Remote Link Child, and takes no `--source`, `--provider`, or `--allow-private-network`. It inspects `auth.json` read-only at the byte level (failing closed if unreadable or malformed) and removes only providers from the active journal that still match their imported keyless state and are not selected as `defaultProvider` or referenced by routing, combos, fallback policy, OAuth accounts, or API keys. ## Authentication ### Diagnosing missing main-account quota `ocx account list openai --quota --refresh --json` includes a `quotaRefresh` object on the main-account row when that operation attempts a WHAM usage read. The existing `GET /api/codex-auth/accounts?refresh=1` response exposes the same diagnostic. Its `status` is `ok`, `not_reported` (no parseable quota in a successful response), `http_error`, `timeout`, `network_error`, `invalid_response`, or `internal_error`. Only `http_error` includes a numeric `httpStatus`. No raw response, error message, credential, or account identifier is included in this object. Cache-only reads, credential deferrals, and invalidated account snapshots omit it; older servers also omit it. Absence is not proof of success. A non-success HTTP status remains `http_error` even if its error body cannot be read; `timeout` and `network_error` describe failures before headers or while reading a successful response. A valid login does not guarantee that this separate usage request succeeds. These categories do not change authentication, account selection, or quota freshness rules, and do not turn unknown quota into zero usage. This diagnostic currently covers the native main account, not pool-account refreshes. When reporting missing quota, share the category and HTTP status rather than credential files or a raw network capture. ### `ocx login ` Start the provider's registered login flow. OAuth-style account providers open a browser and store credentials under `~/.opencodex/` (refreshable tokens rotate automatically; durable key grants such as OrcaRouter are reused until the provider revokes them); API-key login providers open their key dashboard, prompt for the key, validate it when possible, and save the resulting provider config. The command prints the currently accepted OAuth and API-key provider ids when the name is missing or unknown. Failed OAuth logins exit with status 1 and print a fixed diagnostic: the HTTP status and standard OAuth error code (such as `invalid_grant`) when the provider returned one, `network_error` or `timeout` when the provider could not be reached, `configuration_error` with the reason when a local setting such as a Codex account namespace blocks the login, and `oauth_authentication_failed` otherwise. Pool 2 (`anthropic2`) also reports `configuration_error` before prompting when a custom provider owns that name or its config file cannot be read or parsed. Use the same command to **reauthenticate** after `ocx status` / `ocx doctor` reports reauthentication required or a terminal refresh failure (or use Reauthenticate in the dashboard). Codex pool accounts are not one of those OAuth or API-key providers, but `ocx login codex` reaches them anyway: it routes to the account-pool login, so `ocx login codex --reauth` is the same thing as `ocx account reauth codex`. The dashboard Codex account pool (Reauthenticate) does it too. That route runs inside the proxy, so it needs a running one. For the ChatGPT OAuth provider, an explicit terminal OAuth code confirming an expired or revoked refresh grant requires signing in again. Free-text descriptions alone never require reauthentication. Temporary token-service failures, timeouts and cancellation leave the account eligible for a later refresh. Browser token exchange and refresh each have a 30-second per-fetch deadline covering headers and body. HTTP-derived errors show only the status and an allowlisted OAuth code. Cancellation, timeout and transport failures use fixed messages without the original reason or cause. ```bash ocx login xai ocx login anthropic ocx login orcarouter-oauth # browser consent + S256 PKCE ocx login orcarouter # paste an existing API key ocx login anthropic --headless # no browser, no local port ``` `--headless` runs the **code-display** login for hosts whose browser cannot reach the proxy's loopback callback (containers, remote servers). The command prints an authorization URL; approve it in any browser, and the provider shows a `code#state` string to paste back at the prompt. No callback port is bound and no browser is launched. Only `anthropic` and `anthropic2` support it today; any other provider exits with the list of providers that do. For Codex accounts use the device-code login instead, `ocx account login codex --device` — `ocx login codex --headless` is refused rather than passed on. The paste window is 10 minutes, and each login accepts one code: if the exchange fails, start a new login. A successful login becomes the provider's active account, exactly like a browser login. OAuth reauthentication preserves operator settings such as model selections, pricing overrides, and account failover preferences. Login-owned transport/authentication fields and registry-owned catalog metadata are refreshed. A live-discovery provider keeps its selected default model; a static provider can replace a default that no longer exists in its refreshed catalog. For Antigravity, an upstream `401` can refresh the rejected account’s OAuth credential and retry the request once. The retry uses that credential’s Cloud Code Assist project. If refresh fails or no usable project is available, the request returns an authentication error; use the reauthentication flow above. A second `401` does not start another refresh/retry cycle. A `403` asking to verify the account quarantines that credential as `needs-reauth(verify)`; when account failover is enabled, the request can retry on another eligible account. Complete Google's account verification, then run `ocx login google-antigravity`. Silent token refresh does not clear this verification requirement. If OpenCodex cannot save the quarantine, this adapter exchange preserves the original `403` without another recovery send; the account has not been durably quarantined. If a sibling request cannot be built or admitted for sending, the exchange delivers the original `403` through normal error formatting. An enclosing combo or policy route can still apply its existing fallback rules. For fetch-based web-search requests, automatic sibling rotation accepts only a complete, bounded Google error envelope containing `error.details[].reason === "VALIDATION_REQUIRED"`. Verification wording alone does not rotate. This request-scoped recovery uses the sibling's token and project without persisting a `needs-reauth` mark; cancellation, committed output, budget exhaustion or an unavailable sibling preserves the failure. A proxy that is already running picks up the new credential without a restart: the CLI asks it to reload that one provider from disk, and the request carries no credential of its own. If the running proxy cannot accept that request — most often because it started from a build that predates attested reload — the login still succeeds and the credential is still written to disk, but the live process keeps serving the previous one. The CLI says so and asks you to restart: ``` ⚠️ A proxy is running but could not reload this provider (unattested-target). The credential is saved to disk; the running proxy keeps using the previous one. Restart it to pick this up: ocx restart ``` ### `ocx logout ` Remove the stored OAuth credential locally by default. `--json` reports `{schemaVersion:1, ok, provider, removed}`; missing credentials return `reason: "not_found"` and exit 4, while removal returns exit 0. For a requested logout on the running proxy, `ocx logout --live --json` accepts public OAuth providers and returns `{schemaVersion:1, success:true, provider, live:true}`. It does not sign out Codex/native-main or remove one selected account. It never deletes local credentials before the live request or falls back to local deletion after failure. ## Accounts and key pools ### Pool policy, account thresholds and paid-credit intent Read offline help, then the selected target's policy before changing it: ```bash ocx account pool --help ocx account pool openai --json ocx account pool anthropic --json ``` These are live management reads. The response includes `provider`, `kind`, `supported`, nullable stored policy, `enabledEffective` and optional `inert`. Only fields named by `supported` may be written; null is not false, and an inert policy is not active pooling. Threshold zero and `enabled:false` are independent. Use `--enabled on|off`, `--threshold 0..100`, `--strategy`, `--sticky 1..100`, and `--quota-window` only where supported. OpenAI does not support pool enabled/window writes; `reset-first` is for OpenAI and generic OAuth pools (never Anthropic), `least-loaded` Kiro-only, and Anthropic windows are `five-hour`, `weekly`, `max-utilization`. Omitted fields stay unchanged. Read DTO fields such as routes, per-account concurrency or Anthropic `nativeMessages` do not imply a setter in this command; use only the listed CLI options. A save that returns `warning: "config_bookkeeping_failed"` was persisted but needs a fresh read to confirm the live state. ```bash ocx account pool anthropic --threshold 80 --quota-window five-hour --json ocx account pool anthropic --json ocx account auto-switch openai status --account --json ``` An explicit `--account` selects the OpenAI per-account threshold. Actions are `status`, `on` (80), `off` (0), `inherit` (null), or `threshold N` (0–100). Without `--account`, the existing auto-switch command remains pool-scoped. The result is `{ok, id, autoSwitchThresholdOverride, autoSwitchThreshold}`; read the nullable override and observed effective value separately. Paid-credit use after included quota is a distinct policy requiring explicit spending intent. `ocx account credits openai on|off --json` targets one account. `ocx account credits openai --all on|off --json` is exclusive with that selector: on records current selectable IDs plus main, off clears the list. Receipts are `{ok, id, creditsAfterLimit}` or `{ok, all, ids}`; retain the actual returned IDs and read back `ocx account list openai --json`. Showing credit balances with `--show-codex-credits` does not enable this policy. Do not enable credits to recover from a failed login or quota read. ### Quota activation and reset-grant observations ```bash ocx account quota-activation openai --window fiveHour off --json ocx account list openai --json ocx account anthropic-reset-grants --json ``` Quota activation accepts `fiveHour` or `weekly`, deliberately different from Anthropic pool `five-hour`. It writes one account/window toggle; enabling can schedule quota refresh. `{ok, id, window, enabled, available}` reports policy and observed availability, not completed refresh. Unavailable enablement may return 409; disabling is still allowed. It never consumes a reset grant. Anthropic reset-grants accepts an optional exact Anthropic account ID, not a Codex alias/main selector; omission uses the existing active/fallback choice. The GET may contact the upstream status service. It reports eligibility/reason, grant counts, nullable dates, cooldown, pending operation and journal availability. Empty grants are not the same as unavailable status. It never consumes a grant or resumes a pending spend. Consumption remains a human GUI operation. ### Use remaining included quota To keep using a selected account past a proactive switching threshold or the main-account protection threshold, explicitly enable its remaining-quota preference: ```bash ocx account use-remaining openai main on ocx account use openai main ocx account use-remaining openai main status --json ocx account use-remaining openai main off ``` Replace `main` with a unique pool-account ID or alias to target an added account. Enabling this disables proactive usage switching for that account. For the main login, it also moves both effective hard-lock thresholds to 100%, so a 95% reading can still admit a request. The preference persists across restarts until turned off; original threshold settings remain stored and resume applying when it is disabled. It does not select an account itself or override round-robin scheduling, manual/automatic pause, entitlement checks, reauthentication, or upstream cooldowns. Paid-credit permission remains a separate setting; enabling this never opts an account into credits or redeems a reset grant. The default-on main hard lock still refuses at 100%. `GET /api/codex-auth/accounts/use-remaining?id=` reads the live policy. `PUT /api/codex-auth/accounts/use-remaining` accepts `{ id, useRemainingQuota: boolean }`. Both require the existing management authentication. This control is currently exposed through the CLI and API. ### Main-account quota protection In **Codex settings → Multi-auth → Advanced settings**, **Block main account** is on by default beside Ultra Fast. Switching it off applies immediately; turning it back on first shows the consequences, and cancelling does not change the setting. The main-account card shows monitoring, unknown usage, or a current policy block even when Advanced settings is closed. The default follows from what a drained main account does to Codex Desktop: once the ChatGPT account window reports **0%** remaining, Desktop disables its send button and the account stops accepting turns until the window resets. Holding ocx's own traffic below that point keeps the account usable ([#5694](https://github.com/lidge-jun/opencodex/issues/5694)). The cost is Luna Reserve: while the block is in force, Reserve on that main account cannot activate. To let the main account run to exhaustion and hand over to Reserve, turn the switch off. The **5h window and the weekly window each block on their own**: the 5h window reaching 90% or the weekly window reaching 98% blocks immediately, even while the other still has headroom. Monthly-only accounts use their monthly window. The block releases automatically, with the switch still on, once every blocking window reports a fresh reading below its threshold (a 0% reset counts); the next observation at the threshold blocks again. Configure `codexMainAccountHardLockThresholds` in `config.json` with optional `short` and `long` percentages, for example `{ "short": 90, "long": 98 }`. Both must be integers from 80 through 100, and `short` must not exceed `long`; omitted fields use the defaults. The settings API accepts the same object and returns the effective values in `mainAccountHardLock.thresholds`. **While the lock blocks, requests can fall back to another model instead of failing.** The **Fallback while blocked** row sits beside the protection switch and works like the shadow-call row: picking a model — for example `deepseek/deepseek-chat` — turns the reroute on immediately, the optional thinking effort applies to it, and the switch pauses the reroute without forgetting the model. While the block is in force and no Codex account can serve a request for a native Codex model, that request is routed to the fallback model (with the configured effort) instead of being refused. A usable Pool account still serves the request first; provider-qualified selectors, combos, account-qualified rows and Luna Reserve keep their own destinations; and a fallback that itself names a native Codex model is ignored, because rerouting gpt-* to gpt-* would only re-enter the same refusal. The lock itself is unchanged: it releases on its own as before, and the reroute ends with it. Configure `codexMainAccountHardLockFallback` in `config.json` as `{ "model": "deepseek/deepseek-chat", "reasoningEffort": "low" }`; `enabled: false` pauses the reroute while keeping the model. An absent section, an omitted model, or a malformed section means requests are refused as usual; `reasoningEffort` must be a declared effort. The settings API accepts the same object, rejects invalid writes before mutation, and `null` clears the section. The dashboard and `ocx status` show **possible usage outside opencodex** when fresh readings rise by at least one point in the same reset episode without recent main-account activity through this proxy. This is an advisory observation, not proof: a long-running turn can also cause it. The lock may not prevent exhaustion from outside traffic. The process-local notice clears on identity or reset changes and expires after six hours or at the observed reset, whichever comes first. `ocx status --json` exposes the state, effective thresholds, blocking window kind, and optional `externalUsage` warning under `mainAccountHardLock` when live management evidence is available. An unreadable 5h reading cannot hide a weekly block. Unknown usage does not fabricate a zero, and a missing reading does not erase an already measured blocking tuple. A predicted reset time alone does not unlock it. While blocked, recovery checks owned usage metadata on a capped 5/10/20/40/60-minute schedule, so a reset redeemed outside opencodex is noticed before the predicted reset; the first check waits five minutes, checks stay at least five minutes apart, and the next check is pulled in to the latest known blocking reset. A paused or reauthentication-required main receives no checks before its predicted reset. A longer `Retry-After` also delays profile and token preparation. Only fresh valid usage or authoritative window-absence evidence can lift the block. In Pool mode, a quota `--refresh` bypasses cache freshness but still honors failed-read pacing; a deferred read makes no new diagnostic attempt. Other pause, reauthentication, and upstream limits remain independent. Protection treats one fresh valid WHAM usage response as a replacement for the old 5h reading when its primary window explicitly lasts **at least 24 hours** and reports valid usage, or its primary window is explicitly `null` and a measured secondary supplies the weekly usage. In either case, secondary/tertiary windows must be explicitly `null` or explicitly last at least 24 hours and report valid usage. This follows the parser's short/long boundary, so a one-day window qualifies as well as weekly/monthly windows. The current window still uses the same configured long-window threshold (98% by default). This relies on the single reported snapshot; repeated observations are not required. Any omitted window field, an unknown duration, or partial response headers cannot clear a previous block. All-null responses carrying only credits and supplementary monthly-only readings also cannot establish recovery. The proxy checks the stored credential again before applying a delayed response. An unreadable file or replaced bearer cannot update the usage cache, release the lock, or quarantine the new credential, even for the same account with no second quota read. Its parsed ordinary usage can still be returned to the requesting caller, without shared-state updates or recovery evidence. The account card shows the published cached usage, keeping its quota aligned with the lock status; Direct provider quota omits an unpublished response and its older cached report. Conflicting account identities and stale 401/403 replies retain the current cached info and cannot clear or set the current account's reauthentication state. Responses to requests sent with the identified main credential refresh its cached usage from their quota headers, whether the proxy substituted the stored credential or the caller sent the same credential itself. A response is applied only if that credential is still the observed main credential when it arrives; a caller-owned credential for another account or workspace never updates the main account's usage. The persisted option is `"codexMainAccountHardLock"` in OpenCodex's `config.json`. An absent key or `true` means on; only an explicit `false` turns it off, and that is what switching the setting off stores. The default changed here: the policy used to be opt-in and the old switch removed the key when it was turned off, so an install that had switched it off now reads as on. If you want the old behavior, switch it off once to record the opt-out. Protection covers new requests using the identified main account, not the last 2% itself: already-running requests, unmatched caller-owned keyring credentials, and traffic outside the proxy can still spend quota. Added accounts and other providers remain available. With protection on, an owned startup restores the main credential's in-memory identity binding after native-profile recovery and cleanup, so a persisted policy block survives a restart. Caller-owned Direct, exact-main, main-fallback, and main-pin requests can briefly receive 503 while that binding is pending; healthy stored Pool accounts stay eligible throughout. No credential is read from a foreign or unconfirmed service home for this initialization. While this policy blocks main, Luna Reserve on that account is blocked too. Staying below ordinary quota exhaustion may prevent Reserve activation. Disabling the switch restores normal local handling, not additional upstream entitlement. Use the account quota refresh action to obtain a fresh observation; no reset credit is consumed automatically. ### Luna Reserve alongside routed models The optional [authless Desktop mode](/guides/codex-integration/#authless-codex-desktop-opt-in) keeps Desktop's native Reserve-only picker gate inactive. It also disables Desktop's automatic Reserve handling: Reserve is an explicit model choice, not an automatic fallback. Keep the built-in OpenAI provider enabled in ChatGPT-forward mode, enable the account model picker, and configure a public selector for the stored main account. With effective loopback authless mode enabled, `ocx sync` includes `/gpt-reserve` alongside routed provider models. A bare `gpt-reserve`, an added-account selector, and API-key model discovery are not added to the catalog. The authless setting is ignored for remote-client routing or a listener that needs an admission header. When public and local listeners run together, Reserve compatibility applies only to requests admitted by the local listener. An authenticated public request stays on the normal path even if it originates from the same machine; request headers cannot select the local policy. Enable authless Desktop mode with `ocx system settings --desktop-authless on`, run `ocx sync`, then fully quit and reopen Codex Desktop so it reloads the rewritten configuration and catalog. Follow the [canonical authless Desktop workflow](/guides/codex-integration/#authless-codex-desktop-opt-in). Each compatibility request checks a credential-bound server authorization, cached for at most 60 seconds. OpenCodex sends the Reserve capability header on an owned main-account usage read and requires ordinary usage to be disallowed, the Luna Reserve banner, and exactly one allowed Reserve bucket. Missing, denied, stale or mismatched evidence refuses the request; it does not switch accounts or silently use ordinary Luna. Passive usage can revoke authorization but cannot create it. Global cooldown, pause, reauthentication and the main-account hard lock still apply. Turn the hard lock off if you want to use Reserve on an exhausted main account; doing so does not grant server entitlement. This compatibility path supports conversation requests and compaction, not Reserve as a vision or web-search helper or a standalone search-relay model. Choose another model for those helpers. The picker prefers actual Reserve metadata. When none has been observed, it uses an explicitly marked Luna metadata adaptation, following Desktop's Reserve-or-Luna preset mapping. A visible entry is not proof of availability. Desktop source and fixture-backed paths were checked; a live Reserve-active account was not used to validate this compatibility path. ### `ocx account ` List and switch provider accounts and API-key pools through the running proxy. The shipped help surface is: ```text Usage: ocx account ... list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them). history openai [--limit <1-200>] Recent routing decisions for one Codex pool account. current Show the active account or key. use Switch the active credential; 'main' selects the Codex App login, 'auto' clears the selection unless an account carries that id. clear Clear the manual Codex account selection unconditionally. refresh Force-refresh Codex or provider quota reports. auto-switch Control the Codex pool threshold. alias Set or clear an account's display name; '-' clears it. pause Hold an account out of automatic selection. resume Return a paused account to automatic selection. pause-exhausted Pause every account whose quota is spent. clear-cooldown Drop a cooldown the proxy set after an upstream failure. strategy [] Pool placement strategy; least-loaded is Kiro-only. sticky [<1-100>] Requests a bound thread keeps on one account; omit the value to read it. priority [first|earlier|normal|later|last|-100..100|reset] Selection order; omit the value to read it. remove --yes Remove a stored account or key after an existence check. add-key [--label