Skip to content

model_catalog_json is resolved once at startup: context window cannot follow a provider switch, so Codex over-fills the prompt and upstream returns 400 #47917

Open

Description

@verigle

Summary

model_catalog_json is resolved once, at process start. When the provider changes behind a running process — e.g. a local proxy that rewrites config.toml (CC Switch in "proxy takeover" mode) — the process keeps using the old catalog. So per-provider/per-model metadata, most importantly context_window, does not follow the switch.

When the same model name is served by providers with different limits, a single catalog can only hold one number. Codex trusts the larger value, never triggers auto-compaction, and the smaller provider rejects the request.

Observed (live, this session)

Model deepseek-flash; catalog context_window = 1000000 (as advertised by one provider). After switching to a second provider that serves the same model name with a real limit of 262144:

time thread upstream limit prompt tokens
02:42:04 01a0d4b0… 262144 262145
02:47:03 01a0d4be… 262144 262145
02:47:39 01a0d4be… 262144 262145
02:52:33 01a0d4be… 262144 262145

Upstream error:

This model's maximum context length is 262144 tokens.
However, you requested 0 output tokens and your prompt contains at least 262145 input tokens.

Impact

Any thread longer than the smaller provider's limit becomes unusable on that provider, and Codex never auto-compacts because it believes it has ~1M tokens. From the user's side this looks like "the context size doesn't follow the provider", with no visible cause.

Requested

Any of:

  1. per-provider catalog scoping — e.g. model_providers.<id>.model_catalog_json, or per-provider model entries inside one catalog;
  2. re-resolve the model catalog when config.toml changes (the desktop app / app-server currently does not);
  3. at minimum, document that model_catalog_json is read at startup so users know a restart is required after switching providers.

Workaround we use

Set each entry to the minimum across all providers that serve that model name, so the value is safe everywhere:

deepseek-flash      1000000 -> 262144   (effective 249036 at 95%)
deepseek-v4.1-flash  300000 -> 262144   (effective 249036 at 95%)

Cost: on the provider that really allows ~1M, compaction now happens far earlier than necessary.

Related

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    custom-modelIssues related to custom model providers (including local models)
    contextIssues related to context management (including compaction)
    configIssues involving config.toml, config keys, config merging, or config updates
    on Sep 24, 2026
  2. l33tdawg commented on Sep 29, 2026

    @l33tdawg

    More desktop-app evidence, and a workaround:

    The app runs a long-lived app-server daemon (~/.codex/app-server-daemon/, control socket under ~/.codex/app-server-control/) and it survives app restarts. It reads the catalog once, at process start, and never refreshes it: with model_catalog_json set, the manager is a StaticModelsManager, and refresh_if_new_etag is a no-op there.

    Timeline from this machine: the override was written to the catalog at 2026-09-29 17:50, and the daemon had been up since 2026-09-27 08:58. codex debug models (fresh process) saw the new catalog; the daemon served the old one. codex app-server daemon restart fixed it immediately. An app relaunch does nothing, since the daemon is untouched by it.

    This affects more than context_window. In our case the stale snapshot also meant auto_review_model_override never applied, so approvals_reviewer = "auto_review" kept sending codex-auto-review to a provider that doesn't serve it, and every escalation failed closed (see #31255).

  3. babelllll commented on Sep 30, 2026

    @babelllll

    Confirming this behavior and sharing a temporary automated workaround on Windows while upstream hot-reloading is pending:

    Root Cause Confirmation

    In the current architecture, the persistent app-server --managed-daemon resolves config.toml and model_catalog_json strictly once at startup. When external switchers/proxies rewrite these configuration files on disk, the running daemon remains oblivious to the changes. As a result, the in-memory model catalog, reasoning tiers, and context window limits stay locked to the previous provider.

    Automated Workaround (File Watcher + Daemon Hot-Swap)

    Since the app-server automatically recovers and reconnects active client sessions upon restart, we implemented a lightweight background watcher on config.toml and cc-switch-model-catalog.json:

    1. Debounce (4s): Watches the provider signature (model|name|base_url|catalog_sha256) with a 4-second cooldown to ensure external tools finish writing all files.
    2. Graceful Swap & Fallback: Invokes codex.exe app-server daemon stop (with a -HardStop fallback if attached clients delay shutdown) and restarts the daemon with -WindowStyle Hidden to prevent visible console popup storms on child process spawns (related to Windows: app-server daemon opens a visible console window for every hook and shell command it runs #44768).
    3. Seamless Client Recovery: Active Codex TUI / CLI clients experience a brief ~1-2s disconnect and automatically reconnect with the updated model catalog and context limits fully applied.

    A native SIGHUP / RPC signal (e.g. codex app-server daemon reload-config) or automatic file-watching on the catalog would be the ideal long-term solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingconfigIssues involving config.toml, config keys, config merging, or config updatescontextIssues related to context management (including compaction)custom-modelIssues related to custom model providers (including local models)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions