Skip to content

fix(credential-pool): stop lost-update cooldown erasure and unmatched-hint wrong-key quarantine (#65844 salvage) - #70147

Merged
teknium1 merged 3 commits into
mainfrom
salvage/65844-cooldown-races
Jul 23, 2026
Merged

fix(credential-pool): stop lost-update cooldown erasure and unmatched-hint wrong-key quarantine (#65844 salvage)#70147
teknium1 merged 3 commits into
mainfrom
salvage/65844-cooldown-races

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Salvage of #65844 by @drleadflow: two races in credential-pool cooldown state are closed — (1) a cross-process lost update where one process's stale in-memory snapshot, persisted later, erased another process's just-written exhausted/dead cooldown from auth.json, so every process resumed hammering a rate-limited key; (2) an unmatched api_key_hint in mark_exhausted_and_rotate fell through to current()/_select_unlocked() and benched an innocent healthy credential for the full cooldown TTL.

Both premises verified live on current main (post-#69553/#69843/#69494): write_credential_pool merged only entries missing from the caller's snapshot — same-ID entries were overwritten wholesale — and the unmatched-hint fall-through still guessed.

Changes

  • hermes_cli/auth.py (contributor commit, cherry-picked): _merge_disk_cooldown_state() in the write_credential_pool path — adopts the on-disk status only when it is strictly newer (last_status_at) AND still binding (DEAD, or EXHAUSTED with an unexpired cooldown). Token-changed entries are exempt so re-auths never get the old cooldown resurrected; expired cooldowns are not resurrected so the pool's own expiry-clear survives. Best-effort try/except.
  • agent/credential_pool.py (contributor): unmatched hint → rotate to a fresh selection without marking anything, with an INFO log.
  • Tests (contributor): 5 targeted regressions. Follow-up: the new anthropic pool test stubs read_claude_code_credentials like its siblings, so a dev machine's live ~/.claude creds can't seed a third entry and break the assertion outside CI.

Validation

Before After
Process B persists a pre-quarantine snapshot Cooldown erased, key hammered again by all processes Newer on-disk quarantine survives
Pool expiry-clears a cooldown, then persists Stays cleared (not resurrected)
Entry re-authed with new token Fresh credentials never inherit the old cooldown
Hint matches no entry Next healthy key benched for full TTL Fresh selection returned, nothing marked
  • 181 targeted tests green (test_credential_pool.py, test_auth_profile_fallback.py, test_credential_pool_routing.py, test_auth_codex_quota_probe.py, test_auth_codex_provider.py).
  • Live E2E with two real pool instances against one temp auth.json: A quarantines, B's stale persist keeps the quarantine; expiry-clear and token-change escapes both verified to still win.

Semantically compatible with #69494's clear_codex_pool_quota_cooldowns (a cleared status is None, never re-adopted) and orthogonal to #69553/#69843 (hint matching; this covers unmatched fall-through + persistence). Closes #65844's scope. Known residual (from the original review): proxy/adapters/xai.py still calls rotation hint-less — follow-up material.

Credit

Both fixes and all 5 regression tests by @drleadflow (#65844), cherry-picked with authorship preserved. Test-isolation fix added on top.

Infographic

infographic

@teknium1
teknium1 force-pushed the salvage/65844-cooldown-races branch from 1752cbc to c1b46e7 Compare July 23, 2026 15:30
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 78fbca6

all good!

drleadflow and others added 3 commits July 23, 2026 09:11
… quarantine

Two related races in credential-pool cooldown state:

1. Lost update across processes: write_credential_pool merged only
   entries missing from the caller's snapshot; for entries present on
   both sides the caller's in-memory copy won wholesale. A process
   holding a snapshot taken before another process marked a key
   exhausted would, on its next persist (e.g. a round-robin rotation),
   write the key back as healthy — erasing the cooldown so every
   process resumes hammering a rate-limited key. Merge status fields by
   last_status_at recency: adopt the on-disk status only when it is
   strictly newer AND still binding (DEAD, or EXHAUSTED with an
   unexpired cooldown), and never onto re-authed (token-changed)
   entries, so legitimate expiry-clears and fresh logins are preserved.

2. Wrong-key quarantine: when mark_exhausted_and_rotate received an
   api_key_hint that matched no entry, it fell through to
   current()/_select_unlocked() — on a freshly loaded pool that selects
   the NEXT healthy key and benches it for the full cooldown TTL,
   punishing an innocent credential. When a hint is provided but
   unmatched, rotate without marking anything instead of guessing.

Includes regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to the #65844 salvage: the new anthropic pool test must stub
read_claude_code_credentials like the sibling tests, otherwise a dev
machine's live claude_code singleton seeds a third entry and the
no-benching assertion fails outside CI.
@teknium1
teknium1 force-pushed the salvage/65844-cooldown-races branch from c1b46e7 to 78fbca6 Compare July 23, 2026 16:13
@teknium1
teknium1 merged commit c2c2449 into main Jul 23, 2026
37 checks passed
@teknium1
teknium1 deleted the salvage/65844-cooldown-races branch July 23, 2026 16:22
@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 comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants