ChatGPT-auth first turn blocks on stale model/plugin fetches (~3.8s avoidable startup latency)

Open 💬 0 comments Opened Aug 22, 2026 by divagr18

What version of Codex CLI is running?

0.149.0

What subscription do you have?

Plus

What platform is your computer?

Microsoft Windows NT 10.0.19045.0 x64

What issue are you seeing?

With ChatGPT auth, Codex can block for several seconds before the first model request on network-derived metadata that already has usable cached/default state.

I traced a real turn with OTLP spans and found two waits on the first-turn critical path:

  1. When models_cache.json is older than the 5-minute freshness window, OnlineIfUncached synchronously calls list_models. In my trace this took ~2.5s and blocked thread/start, even though the cached catalog was version-compatible and usable.
  2. fetch_recommended_plugins can be awaited during turn/tool preparation when plugin state has not been cached yet, potentially blocking the turn up to the fetch timeout.

On my baseline trace, the first model request started at 5124ms.

What steps can reproduce the bug?

Environment:

  • Windows NT 10.0.19045.0 x64
  • ChatGPT auth
  • Cargo release build of current main

For the models-cache path:

  1. Run Codex once so models_cache.json exists.
  2. Let the cache become older than the 5-minute freshness window.
  3. Start a new codex exec turn.
  4. Capture startup with OTLP tracing.

The trace shows an online list_models request (~2.5s in my run) inside thread/start before the turn can proceed.

For recommended plugins, start a turn before recommended-plugin state has been populated. fetch_recommended_plugins is awaited on the turn/tool-preparation path rather than allowing the turn to proceed with cached/default state.

What is the expected behavior?

A version-compatible cached model catalog should be usable immediately even if its TTL has expired, with revalidation happening in the background.

Likewise, recommended-plugin discovery should not block the current turn when cached/default plugin state is sufficient. The refreshed state can be used by subsequent turns.

Network metadata refreshes should not delay the first model request when usable local state already exists.

Additional information

I also built a reference implementation to validate that these waits can be moved off the first-turn critical path:

  • models-manager: serve a version-compatible stale cache immediately and revalidate it in the background, preserving ETag/cache rewrite behavior.
  • core-plugins: use cached plugin state for the current turn while recommended-plugin discovery runs asynchronously.

This also applies to codex debug models, which uses raw_model_catalog(OnlineIfUncached) for model discovery, so stale-cache discovery through that path benefits from the same change.

Branch:
https://github.com/divagr18/codex/tree/perf-stale-models-cache-nonblocking-plugin-discovery

Compare:
https://github.com/openai/codex/compare/main...divagr18:codex:perf-stale-models-cache-nonblocking-plugin-discovery

A/B on identical real-config turn (OTLP spans):

| Metric | Before | After | Delta |
| --- | ---: | ---: | ---: |
| First model request | 5124ms | 1287ms | -3.8s (-75%) |
| thread/start | 2638ms | 147ms | -2.5s |
| Turn starts | 2917ms | 382ms | -2.5s |
| Total wall | 14.5s | 9.5s | -5.0s |

Background model revalidation still completed and rewrote the cache with the fresh ETag.

Validation:

  • models-manager: 50/50 tests
  • core-plugins: 402/402 tests
  • cargo clippy --tests clean on touched crates
  • coverage added for stale-cache serving and non-blocking plugin discovery

The measured A/B improvement is primarily from removing the synchronous model-list refresh; the plugin change covers the additional wait when plugin discovery has not already completed.

Raw OTLP traces are available if useful.

Related: #28877, #18663

View original on GitHub ↗