Repository navigation
Bare gpt-reserve gets no catalog row outside codexDesktopAuthless: tool_mode falls back to direct, ~250k tokens of inlined tool schemas at boot #3844
Description
Summary
The bare gpt-reserve wire id gets no catalog row outside codexDesktopAuthless mode. Codex then falls back to default model settings, which means tool_mode is unset (i.e. direct) instead of code_mode_only. Every MCP/plugin tool schema is inlined into the request instead of being deferred behind the exec namespace.
On a Codex Desktop install with the normal plugin set this is ~250k input tokens on turn 1, against a fallback model_context_window of 258400. The session compacts after nearly every action.
Environment
- opencodex 2.45.0 (also reproduced on 2.42.0)
- Codex 0.153.4, Codex Desktop 26.901.51231, macOS 15 (arm64)
- Normal loopback routing: root
openai_base_url = http://127.0.0.1:10100/v1,codexDesktopAuthlessunset model_catalog_json = ~/.codex/opencodex-catalog.json(39 models, nogpt-reserve)
Evidence
Turn 1 of a fresh Desktop thread, prompt "hi", from the rollout token_count event:
{"input_tokens": 252450, "cached_input_tokens": 3328, "output_tokens": 94,
"model_context_window": 258400}~/.opencodex/usage.jsonl confirms the wire id resolves fine upstream — the request itself succeeds:
{"model": "gpt-reserve", "resolvedModel": "gpt-5.6-luna",
"requestedEffort": "max->xhigh", "status": 200, "totalTokens": 252544}Minimal repro
Point Codex at a request sink and compare a catalogued slug against gpt-reserve:
codex exec --config openai_base_url='"http://127.0.0.1:10199/v1"' -m <slug> "hi"
-m slug |
in catalog | request body | inlined tools[] |
additional_tools deferral |
|---|---|---|---|---|
gpt-reserve |
no | 1,321,509 chars | 56 | no |
zzz-fake-model |
no | 1,321,512 chars | 56 | no |
gpt-5.6-luna |
yes | 120,914 chars | 0 | yes |
gpt-5.6-terra |
yes | 120,915 chars | 0 | yes |
gpt-reserve behaves identically to a nonsense slug. Breakdown of the 56 inlined tools under gpt-reserve (CLI only — Desktop is larger):
| source | tools | ~tokens |
|---|---|---|
| codex_apps | 35 | 292,716 |
| builtin | 15 | 6,817 |
| dataAnalyticsWidgets | 1 | 6,203 |
| playwright | 1 | 4,029 |
| others | 4 | 3,110 |
Note that [features] tool_search = true and tool_search_always_defer_mcp_tools = true are set in config.toml and do not help — the per-model tool_mode field is the gate.
Cause
createReserveCatalogProjection() in src/codex/catalog/reserve.ts returns early:
if (!isEffectiveCodexDesktopAuthless(config) || mainSelectors.length === 0) return undefined;Two consequences for a user on normal openai_base_url routing:
- The projection never fires, so no Reserve row is emitted at all.
- Even when it does fire, the row is account-bound (
<selector>/gpt-reserveperisReserveCatalogProjection), while the Desktop model picker sends the baregpt-reserve.
native-models.ts is explicit that this is intended — "Reserve wire identity, not a globally available native catalog registration." But the account still serves the bare slug upstream (200 above), so a user can select it and silently lose both tool deferral and the correct context window with no warning anywhere.
Sync does recognise a hand-added bare row: observedReserveCatalogSource() captures it into the root opencodex_reserve_source field. It just never emits a model row from it outside the authless gate, so ocx sync strips the row on every run (including the auto-sync on ocx start).
Workaround
Clone the gpt-5.6-luna row with slug: "gpt-reserve" into opencodex-catalog.json. That restores tool_mode: code_mode_only, context_window: 300000, effective_context_window_percent: 95, auto_compact_token_limit: 270000, and the request drops to 120,912 chars with 0 inlined tools — identical to Luna. It has to be re-applied after every sync.
Suggested fix
Either:
- Emit a bare
gpt-reserverow whenever a genuine reserve observation exists (opencodex_reserve_sourceis populated), independent ofcodexDesktopAuthless; or - If Reserve is genuinely unsupported outside authless mode, warn on sync when the persisted Codex model selection is a slug with no catalog row — silently degrading to
tool_mode: directand a smaller window is very hard to attribute.
More generally: any slug the backend accepts but the catalog omits will hit this. A sync-time warning for "selected model has no catalog row" would catch the whole class.
Activity
github-actions commentedon Sep 7, 2026
Issue closed: use an issue template
Thanks for taking the time to submit this issue. It was closed automatically because it was not opened with a recognized issue template (Bug report, Feature request, Documentation, or Provider compatibility). Freeform and API-opened issues cannot skip this check.
Please edit the issue to add:
See the Contributing guide for details.
Once the report passes the automated checks, it will be reopened automatically unless a maintainer has changed its state.