fix(models): keep an observed Codex catalog cap when a refresh fails

After the 1h context TTL, a failed catalog refresh sent resolution to the
static fallback, which applied the -900k bump without the max_context_window
the same token and endpoint had already published. A known 600K cap became
900K with no newer evidence. The fallback branch now passes the cached cap.
Introduced with the cap itself in #115872.

Reported by andrexibiza on #128288.
This commit is contained in:
kshitijk4poor
2026-10-01 21:50:04 +05:30
committed by kshitij
parent e0a8749ca0
commit 03487e9ed9
2 changed files with 26 additions and 3 deletions

View File

@@ -1914,15 +1914,20 @@ def _resolve_codex_oauth_context_length_with_source(model: str, access_token: st
# main-agent path normalizes it away before reaching here, but display/auxiliary callers pass it through
# (#92797 review).
lookup_bare = _bare_codex_slug(strip_codex_context_variant_suffix(model_bare))
live_max: Dict[str, int] = {}
def _catalog_key(keys) -> Optional[str]: # exact slug, then case-insensitive in case casing drifts
return lookup_bare if lookup_bare in keys else next((s for s in keys if s.lower() == lookup_bare.lower()), None)
if access_token:
live, fresh_probe = _fetch_codex_oauth_context_lengths_with_source(access_token, base_url=base_url)
live_max = _codex_oauth_max_context_cache.get(_codex_oauth_token_fingerprint(access_token, base_url), {})
# Exact slug, then case-insensitive in case casing drifts.
slug = lookup_bare if lookup_bare in live else next((s for s in live if s.lower() == lookup_bare.lower()), None)
slug = _catalog_key(live)
if slug is not None:
return _apply_verified_bump(live[slug], "live" if fresh_probe else "memory", live_max.get(slug))
hit = _longest_key_match(_CODEX_OAUTH_CONTEXT_FALLBACK, lookup_bare.lower())
return _apply_verified_bump(hit[1], "fallback") if hit else (None, "")
# A failed refresh past the TTL must not lift an already observed catalog cap.
return _apply_verified_bump(hit[1], "fallback", live_max.get(_catalog_key(live_max))) if hit else (None, "")
def _resolve_nous_context_length(model: str, base_url: str = "", api_key: str = "") -> Tuple[Optional[int], str]:

View File

@@ -428,6 +428,7 @@ class TestCodexOAuthContextLength:
def setup_method(self):
import agent.model_metadata as mm
mm._codex_oauth_context_cache = {}
mm._codex_oauth_max_context_cache = {} # the fallback branch reads it too
def test_live_catalogue_cache_is_scoped_to_access_token(self):
@@ -680,6 +681,23 @@ class TestCodexOAuthContextLength:
)
assert ctx == 272_000
def test_failed_refresh_past_ttl_keeps_the_observed_catalog_cap(self):
"""A refresh that fails after the context TTL must not lift a cap the same token and
endpoint already published: no newer evidence, so the -900k alias stays at the old max."""
import agent.model_metadata as mm
ok = MagicMock(status_code=200)
ok.json.return_value = {"models": [{"slug": "GPT-5.6-Luna", "context_window": 272_000, "max_context_window": 600_000}]}
route = dict(base_url="https://chatgpt.com/backend-api/codex", api_key=_codex_jwt("fake-token"), provider="openai-codex")
with patch.object(mm, "_codex_oauth_context_cache", {}), patch.object(mm, "_codex_oauth_max_context_cache", {}), \
patch("agent.model_metadata.get_cached_context_length", return_value=None), \
patch("agent.model_metadata.save_context_length"):
with patch("agent.model_metadata.model_metadata_http.get", return_value=ok):
assert mm.get_model_context_length(model="gpt-5.6-luna-900k", **route) == 600_000
with patch("agent.model_metadata.model_metadata_http.get", side_effect=OSError("catalog down")), \
patch("agent.model_metadata.time.time", return_value=time.time() + mm._CODEX_OAUTH_CONTEXT_CACHE_TTL + 1):
assert mm.get_model_context_length(model="gpt-5.6-luna-900k", **route) == 600_000
def test_catalog_max_is_published_before_the_context_entry(self):
"""A reader that sees a fresh context entry must already be able to see its cap, or a
concurrent -900k resolution between the two writes skips the catalog max."""