fix(credential-pool): stop lost-update cooldown erasure and wrong-key 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>
This commit is contained in:
Blade 2026-07-16 12:55:33 -04:00 committed by Teknium
parent fdd3943cb1
commit 3d67f00fe1
4 changed files with 276 additions and 1 deletions

View file

@ -1447,6 +1447,73 @@ def read_credential_pool(provider_id: Optional[str] = None) -> Dict[str, Any]:
return list(global_entries) if isinstance(global_entries, list) else []
_POOL_STATUS_FIELDS = (
"last_status",
"last_status_at",
"last_error_code",
"last_error_reason",
"last_error_message",
"last_error_reset_at",
)
def _merge_disk_cooldown_state(
entry: Dict[str, Any],
disk_entry: Optional[Dict[str, Any]],
provider_id: str,
) -> Dict[str, Any]:
"""Keep a newer on-disk cooldown/quarantine over a stale in-memory one.
``write_credential_pool`` callers persist an in-memory snapshot that may
predate another process marking the same credential exhausted or dead
(last-writer-wins lost update). Without this merge, process B's later
rewrite resurrects a rate-limited key as healthy and both processes
resume hammering it. Adopt the on-disk status fields only when they are
strictly more recent (by ``last_status_at``) AND still binding a DEAD
marker, or an EXHAUSTED cooldown that has not yet expired. Expired
cooldowns are not resurrected, so the pool's own expiry-clear (which
resets ``last_status_at`` to None) is never overridden.
"""
if not isinstance(disk_entry, dict):
return entry
try:
from agent.credential_pool import (
PooledCredential,
STATUS_DEAD,
STATUS_EXHAUSTED,
_exhausted_until,
_parse_absolute_timestamp,
)
disk_status = disk_entry.get("last_status")
if disk_status not in (STATUS_DEAD, STATUS_EXHAUSTED):
return entry
# A token change means the caller re-authed/refreshed this entry and
# intentionally cleared its status (e.g. _sync_codex_entry_from_
# auth_store after a fresh device-code login) — never resurrect the
# old cooldown onto fresh credentials.
mem_access = entry.get("access_token") or ""
disk_access = disk_entry.get("access_token") or ""
if mem_access and disk_access and mem_access != disk_access:
return entry
disk_ts = _parse_absolute_timestamp(disk_entry.get("last_status_at")) or 0.0
mem_ts = _parse_absolute_timestamp(entry.get("last_status_at")) or 0.0
if disk_ts <= mem_ts:
return entry
if disk_status == STATUS_EXHAUSTED:
until = _exhausted_until(
PooledCredential.from_dict(provider_id, disk_entry)
)
if until is None or until <= time.time():
return entry
merged_entry = dict(entry)
for status_field in _POOL_STATUS_FIELDS:
merged_entry[status_field] = disk_entry.get(status_field)
return merged_entry
except Exception: # pragma: no cover - best-effort merge
return entry
def write_credential_pool(
provider_id: str,
entries: List[Dict[str, Any]],
@ -1464,6 +1531,10 @@ def write_credential_pool(
the caller loaded its in-memory snapshot; without this merge a later
rotation/exhaustion rewrite drops the concurrent credential.
For entries present on BOTH sides, status fields are merged by
``last_status_at`` recency via ``_merge_disk_cooldown_state`` so a stale
snapshot cannot erase a cooldown/quarantine another process just wrote.
Pass ``removed_ids`` for entries the caller intentionally removed, so the
merge does not resurrect them from the on-disk copy.
"""
@ -1481,12 +1552,24 @@ def write_credential_pool(
]
existing = pool.get(provider_id)
existing_list = existing if isinstance(existing, list) else []
existing_by_id = {
entry.get("id"): entry
for entry in existing_list
if isinstance(entry, dict) and entry.get("id")
}
new_ids = {
entry.get("id")
for entry in sanitized_entries
if isinstance(entry, dict) and entry.get("id")
}
merged: List[Dict[str, Any]] = list(sanitized_entries)
merged: List[Dict[str, Any]] = [
_merge_disk_cooldown_state(
entry, existing_by_id.get(entry.get("id")), provider_id
)
if isinstance(entry, dict)
else entry
for entry in sanitized_entries
]
for disk_entry in existing_list:
if not isinstance(disk_entry, dict):
continue