ChatGPT-auth first turn blocks on stale model/plugin fetches (~3.8s avoidable startup latency)
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:
- When
models_cache.jsonis older than the 5-minute freshness window,OnlineIfUncachedsynchronously callslist_models. In my trace this took ~2.5s and blockedthread/start, even though the cached catalog was version-compatible and usable. fetch_recommended_pluginscan 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:
- Run Codex once so
models_cache.jsonexists. - Let the cache become older than the 5-minute freshness window.
- Start a new
codex execturn. - 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
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 testscore-plugins: 402/402 testscargo clippy --testsclean 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