Skip to content

credential pool: adopt the rotated token when the agent's stale key matches no entry (xai-oauth fallback 2026-09-19) - #730

Merged
Kyzcreig merged 1 commit into
mainfrom
fix/pool-adopt-rotated-single-entry
Sep 19, 2026
Merged

Kyzcreig merged 1 commit into
mainfrom
fix/pool-adopt-rotated-single-entry

Conversation

@Kyzcreig

Copy link
Copy Markdown
Collaborator

Incident

2026-09-19 03:16 PDT, Discord banner 🔄 Model fallback (auth refresh): xai-oauth/grok-4.6 → claude-apx-1/claude-opus-5.
A long-lived gateway session 403'd on xai-oauth at 03:49:52 while a cron session succeeded on grok-4.6 at 03:49:55.
The pool held a valid, freshly rotated pair; the agent held the pre-rotation access token in agent.api_key
(xAI refresh tokens are single-use, so another process had legitimately rotated the pair).

Root cause (recovery path)

  1. stale key matched no pool entry → try_refresh_matching() returned None (no refresh attempted)
  2. unmatched-rotation branch → single-entry escape → None
  3. fallback fired despite a usable token sitting in the pool

Fix — agent/credential_pool.py

  • _adopt_rotated_entry_unlocked() (new): failing key matches no entry, but an entry holds a different, unexpired token → adopt it for one retry. xai-oauth entries are resynced from the auth store first. Nothing is marked exhausted; the unmatched-rotation streak is not reset, so a second unmatched failure still trips the existing > max(available_count,1) bound and fallback proceeds.
  • mark_exhausted_and_rotate() single-entry escape tries adoption before giving up.
  • try_refresh_matching(): id-bound entry already holds a different token than the hint → adopt instead of force-refreshing (a force refresh would burn the fresh single-use refresh token).

Multi-entry behavior unchanged. Nous-style wrapper keys (hint never matches by design) get exactly one extra bounded retry in single-entry pools before fallback.

Tests

  • New tests/agent/test_credential_pool_adopt_rotated_entry.py — 8 tests; 3 fail on fork/main without the fix, pass with it.
  • test_unmatched_key_does_not_retry_only_pool_entry updated to the bounded adopt-once contract (docstring cites the incident).
  • tests/agent -k "credential or auth or pool or refresh or fallback" → 934 passed, 2 skipped
  • tests/run_agent -k "credential or pool or auth" → 135 passed

Layer 2 (proactive xai-token-keeper + launchd) and Layer 3 (provider parity across profiles + lint) land separately as fleet scripts, not in this repo.

…atches no entry

Incident 2026-09-19 03:16 PDT: a long-lived gateway session fell back
xai-oauth/grok-4.6 -> claude-apx-1/claude-opus-5 on a 403 while a cron
session succeeded on the same provider three seconds later. The pool held
a valid, freshly rotated pair; the agent held the pre-rotation access
token in agent.api_key (xAI refresh tokens are single-use, so another
process had legitimately rotated the pair underneath it).

Recovery path failure: the stale key matched no pool entry, so
try_refresh_matching() returned None (no refresh attempted), the unmatched
rotation branch hit the single-entry escape and returned None, and the
fallback fired despite a usable token sitting in the pool.

Fix (agent/credential_pool.py):
- New _adopt_rotated_entry_unlocked(): when the failing key matches no
  entry but an entry holds a DIFFERENT, unexpired token, adopt it for one
  retry (xai-oauth entries are resynced from the auth store first). Nothing
  is marked exhausted; the unmatched-rotation streak is NOT reset, so a
  second unmatched failure still trips the existing bound and lets the
  fallback proceed.
- Single-entry escape in mark_exhausted_and_rotate() now tries adoption
  before giving up.
- try_refresh_matching(): when the id-bound entry already holds a different
  token than the hint, adopt it instead of force-refreshing (which would
  burn the fresh single-use refresh token).

Tests: new tests/agent/test_credential_pool_adopt_rotated_entry.py (8
tests; 3 fail on fork/main without the fix) and
test_unmatched_key_does_not_retry_only_pool_entry updated to the new
bounded adopt-once contract. tests/agent -k "credential or auth or pool
or refresh or fallback": 934 passed; tests/run_agent -k "credential or
pool or auth": 135 passed.
@Kyzcreig
Kyzcreig enabled auto-merge September 19, 2026 11:11
@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

running on 441b6f4 — credential pool: adopt the rotated token when the agent's st


Still running 13 jobs: Python tests / Run tests slice 1/16, Python tests / Run tests slice 10/16, Python tests / Run tests slice 11/16, Python tests / Run tests slice 12/16, Python tests / Run tests slice 13/16, Python tests / Run tests slice 14/16, Python tests / Run tests slice 15/16, Python tests / Run tests slice 16/16, Python tests / Run tests slice 2/16, Python tests / Run tests slice 6/16, Python tests / Run tests slice 7/16, Python tests / Run tests slice 8/16, Python tests / Run tests slice 9/16

❌ Job failures

Python tests / Run tests slice 3/16 · View job

Job Python tests / Run tests slice 3/16 failed.


⚠️ Warnings

OSV vulnerability scan · View job

82 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

@Kyzcreig
Kyzcreig added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit def909c Sep 19, 2026
185 of 229 checks passed
@Kyzcreig
Kyzcreig deleted the fix/pool-adopt-rotated-single-entry branch September 19, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant