Skip to content

fix(context): revalidate Codex OAuth context windows - #61737

Closed
sbe27 wants to merge 4 commits into
NousResearch:mainfrom
sbe27:fix/codex-context-cache-revalidation
Closed

sbe27 wants to merge 4 commits into
NousResearch:mainfrom
sbe27:fix/codex-context-cache-revalidation

Conversation

@sbe27

@sbe27 sbe27 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Bug Description

get_model_context_length() could return a stale value from
~/.hermes/context_length_cache.yaml before reaching the authenticated Codex
/backend-api/codex/models resolver. A fallback such as 272000 could therefore
be persisted after a transient probe failure and continue to mask a later live
allocation (for example, 372000) across restarts, /model, and model-picker
changes for the same model/base URL.

Fixes #

Root Cause

The generic persistent context cache was checked before the provider-specific
Codex OAuth resolver. Codex fallback values and live catalogue values were both
persisted without source information, so a cache hit prevented revalidation.

Fix

  • Bypass the generic persistent context cache for openai-codex, while retaining
    the existing in-process Codex catalogue cache.
  • Track whether a Codex resolution came from the live authenticated catalogue
    or the static fallback table.
  • Persist only values confirmed by the live /models response; fallback values
    remain runtime-only and cannot poison future probes.
  • Leave other providers' persistent-cache behavior unchanged. Existing legacy
    Codex rows are bypassed and replaced on the next successful live resolution.

How to Verify

  1. Seed the persistent cache with:
    gpt-5.6-terra@https://chatgpt.com/backend-api/codex: 272000.
  2. Resolve the model with a mocked authenticated /models response containing
    context_window: 372000.
  3. Confirm the resolver calls the live endpoint, returns 372000, and replaces
    the cache entry with 372000.
  4. Resolve with a failed/unauthorized live probe and confirm the runtime falls
    back to 272000 without writing that fallback to disk.

Test Plan

  • Added regression test for this bug
  • Existing targeted tests still pass
  • Manual verification of the live-value and failed-probe cache behavior

Validation:

  • scripts/run_tests.sh tests/agent/test_model_metadata.py tests/hermes_cli/test_model_switch_context_display.py tests/hermes_cli/test_apply_model_switch_result_context.py tests/hermes_cli/test_context_switch_guard.py tests/hermes_cli/test_gpt56_registration.py -q
  • Result: 145 passed
  • git diff --check and Python compilation passed.

Risk Assessment

Low — the change is scoped to the openai-codex provider. Explicit context
configuration remains higher priority, and other providers retain their
existing persistent-cache behavior. When the Codex endpoint is unavailable,
the conservative static fallback is still used for the current resolution but
is no longer persisted as authoritative metadata.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/openai OpenAI / Codex Responses API labels Jul 10, 2026
@sbe27

sbe27 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed the three minor notes from internal review; they are valid observations, with one clarity-only follow-up:

  • The live-value test intentionally does not mock save_context_length: it verifies the end-to-end overwrite of the stale temporary YAML entry. The failure-path tests mock the writer because they specifically assert that fallback values are not persisted. Added an inline comment documenting this distinction.
  • The in-process Codex catalogue cache remains unchanged (_CODEX_OAUTH_CONTEXT_CACHE_TTL = 3600, one hour). Only the persistent disk-cache lookup is bypassed.
  • The tests use the canonical no-trailing-slash URL; _context_cache_key() strips trailing slashes, so both forms map to the same key.

Follow-up commit: 29ec8a99e
Validation: scripts/run_tests.sh tests/agent/test_model_metadata.py -q — 120 passed.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the persistent fallback poisoning; the main premise is confirmed on current main.

Problems

  • agent/model_metadata.py:1856-1876 keeps one process-global Codex catalogue cache, not keyed by credential. The new persistence gate at agent/model_metadata.py:2329 labels an in-process cache hit as "live", so a later credential can persist the first account's entitlement-specific value. Please key that cache by credential (without retaining raw credentials) or distinguish a fresh probe from an in-process hit, with a two-token regression test.
  • cli.py:12179-12181 still invokes the resolver without provider=self.provider. Because the PR's disk-cache bypass is provider-gated before provider inference (agent/model_metadata.py:2109), this @-reference path can still return a stale Codex row. Please propagate the provider and cover that path.

Suggested changes

  • Preserve the disk-cache fix, but make provenance account-safe before allowing persistence.
  • Add the missing CLI provider propagation and regression coverage.

Automated hermes-sweeper review.

Comment thread agent/model_metadata.py
# persist. The static fallback is deliberately runtime-only so a
# transient OAuth/network failure cannot poison future probes.
if base_url and codex_source == "live":
save_context_length(model, base_url, codex_ctx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex_source == "live" also covers _fetch_codex_oauth_context_lengths()'s process-global one-hour cache, which is not keyed by credential (agent/model_metadata.py:1856-1876). Since this PR treats the catalogue as entitlement-specific, a second account can persist the first account's cached value here. Please credential-scope the cache or distinguish a fresh probe from an in-process cache hit before writing to disk.

@sbe27

sbe27 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Teknium's review findings in 63d3223fd:

  • Credential-scoped the in-process Codex catalogue cache using a truncated SHA-256 access-token fingerprint; raw OAuth tokens are not retained in cache keys.
  • Added fresh-probe vs same-token memory-hit provenance. Only fresh authenticated /models responses may update persistent context metadata.
  • Propagated provider=self.provider through the CLI @-context resolver path so Codex cannot be mistaken for a generic provider before disk-cache bypass logic runs.
  • Added regressions for two-token entitlement isolation and the real CLI @-context call path.

Validation:

  • scripts/run_tests.sh tests/agent/test_model_metadata.py tests/cli/test_cli_codex_context_reference.py ... — 147 passed.
  • scripts/run_tests.sh tests/agent/ -q — 5,460 passed, 0 failed.
  • git diff --check and Python compilation passed.

@teknium1 teknium1 added sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 11, 2026

@NoxsMedia NoxsMedia left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated the latest PR head merged cleanly onto current origin/main@8a5f8379e.

What I checked:

  • The credential-fingerprinted in-process catalogue cache prevents one OAuth account's entitlement metadata from being reused as fresh evidence for another account.
  • The live / memory / fallback provenance split ensures only a fresh authenticated /models response can update persistent metadata.
  • The CLI @-context path now propagates provider=self.provider, so Codex cannot hit the generic disk-cache path there.
  • scripts/run_tests.sh tests/agent/test_model_metadata.py tests/cli/test_cli_codex_context_reference.py tests/hermes_cli/test_model_switch_context_display.py tests/hermes_cli/test_apply_model_switch_result_context.py tests/hermes_cli/test_context_switch_guard.py tests/hermes_cli/test_gpt56_registration.py -q passed: 148 tests.
  • py_compile, Ruff, and git diff --check passed on the merged review tree.
  • Live regression probe against the current Codex catalogue: seeded a temporary gpt-5.6-sol disk entry at 372000; the PR bypassed it, resolved 272000 from authenticated /models, and persisted 272000.

No blocking findings.

Non-blocking test suggestion: parameterize the stale-cache reconciliation regression in both directions (272000 -> 372000 and 372000 -> 272000). The implementation already handles both; the reverse case now reflects the current provider rollback and would guard future product-limit changes explicitly.

@sbe27

sbe27 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @NoxsMedia for the thorough review. I've added test coverage for Codex context changes in both directions (272k → 372k and 372k → 272k), including the recent rollback case.

@teknium1

Copy link
Copy Markdown
Collaborator

Merged via PR #68554. Your four commits were cherry-picked onto current main with your authorship preserved in git log — thanks for a well-built fix (the source-tracked persistence and per-credential catalog cache were exactly right). We added one small follow-up on top so the autoraise notice reports the live-resolved window instead of a hardcoded 272K.

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

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/openai OpenAI / Codex Responses API sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants