Skip to content

fix(credential-pool): exhaust all entries sharing a failed API key (#68565 salvage) - #70135

Merged
teknium1 merged 3 commits into
mainfrom
salvage/68565-same-key-siblings
Jul 23, 2026
Merged

fix(credential-pool): exhaust all entries sharing a failed API key (#68565 salvage)#70135
teknium1 merged 3 commits into
mainfrom
salvage/68565-same-key-siblings

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Salvage of #68565 by @airclear: mark_exhausted_and_rotate(api_key_hint=...) now exhausts every pool entry sharing the failed API key, not just the first match. A 402/429/401 is a key-level failure — when the same key backs multiple entries (e.g. an explicit pool entry plus the model_config entry auto-seeded from model.api_key), marking only one left _select_unlocked() handing back the identical depleted key forever: the billing-recovery loop never converged and the request hung ~2.5 min with no error surfaced.

Premise verified empirically: the PR's regression test fails on unpatched main (rotation returns the same-key sibling) and passes with the fix.

Changes

  • agent/credential_pool.py (contributor commit, cherry-picked): after marking the hinted entry, sweep siblings with the same runtime_api_key and exhaust them too. Per-entry _mark_exhausted preserves DEAD-vs-EXHAUSTED classification; guarded by if api_key_hint, so hint-less callers are unchanged.
  • agent/credential_pool.py (follow-up): _mark_exhausted(persist=False) + one batched _persist() for the sibling sweep — one auth.json write instead of one per sibling.
  • tests/agent/test_credential_pool.py (contributor): regression test with two entries backed by one key.

Validation

Before After
402 on a key backing 2 entries Sibling stays OK → same key re-selected → ~2.5 min hang, no error Both exhausted, rotation moves to the next distinct key
All keys exhausted Loop never converges mark_exhausted_and_rotate returns None, error propagates
  • 109 targeted tests green (test_credential_pool.py, test_credential_pool_routing.py, test_credential_pool_interrupt.py).
  • Live E2E with a real 3-entry pool (two sharing a key) in a temp HERMES_HOME: both same-key entries exhausted + persisted in one write, rotation lands on the distinct key, and exhausting that converges to None.

Closes #68565's scope. Complements #69553/#69843 (attribution) — this covers the key-to-many-entries direction.

Credit

Core fix and regression test by @airclear (#68565), cherry-picked with authorship preserved. Persistence batching added on top.

Infographic

infographic

李航 and others added 3 commits July 23, 2026 08:09
… 402

A 402/429/401 is an API-key–level failure (account out of balance,
rate-limited, or key rejected), but the same key can back more than one
pool entry — e.g. an explicit pool entry plus a `model_config` entry
auto-seeded from `model.api_key`, both carrying the identical
`runtime_api_key`.

`mark_exhausted_and_rotate(api_key_hint=...)` only marked the *first*
matching entry, leaving the sibling OK. `_select_unlocked()` then kept
handing back the same depleted key, so the billing-recovery `continue`
loop in the conversation retry path never converged: the request hung
until the client disconnected (~2.5min observed against DeepSeek),
emitting only `response.created` with no 402 ever surfaced to the user.

Mark every entry sharing the failed key so the pool can reach the
"no available entries" state and let the error propagate immediately.

Adds a regression test covering two entries backed by the same key.
Follow-up to the #68565 salvage: batch the sibling _mark_exhausted calls
behind a single _persist() instead of one auth.json write per sibling.
@teknium1
teknium1 force-pushed the salvage/68565-same-key-siblings branch from 2376d43 to f4919db Compare July 23, 2026 15:09
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on f4919db

all good!

@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 area/auth Authentication, OAuth, credential pools sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 23, 2026
@teknium1
teknium1 merged commit 76d4b65 into main Jul 23, 2026
40 checks passed
@teknium1
teknium1 deleted the salvage/68565-same-key-siblings branch July 23, 2026 16:08
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 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.

2 participants