fix(agent): keep credential-pool entries when terminal-OAuth quarantine save fails - #63131
fix(agent): keep credential-pool entries when terminal-OAuth quarantine save fails#63131roryford wants to merge 1 commit into
Conversation
…ne save fails xAI, Codex, and Nous terminal-OAuth-refresh paths evicted the in-memory pool entry and persisted even when the auth-store clear/save raised, leaving the pool diverged from on-disk auth.json. Gate the eviction and persist behind a `cleared` flag that is only set once the auth-store write path completes without error, and raise the failure log level from debug to warning so it's visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
teknium1
left a comment
There was a problem hiding this comment.
Thanks for covering all three terminal-refresh quarantine paths. The underlying bug is present on current main: failed auth-store clears are caught and followed by unconditional in-memory removal and persistence (agent/credential_pool.py:1222-1236, :1292-1306, :1356-1373).
Problems
- The proposed
cleared = Trueis outside the nested write guard. For xAI,_save_auth_store()only runs whenstate["tokens"]is a dict (agent/credential_pool.py:1203-1221); Codex has the equivalent guard at:1273-1291. If that nested state is malformed, no save occurs but the proposed flag still permits eviction, preserving the divergence this PR is intended to prevent.
Suggested changes
- Assign
cleared = Trueimmediately after the actual_save_auth_store(auth_store)call in each successful quarantine branch. - Add a malformed-nested-state regression test for the xAI/Codex guard.
This is an automated hermes-sweeper review.
| @@ -1165,21 +1166,23 @@ def _refresh_entry_impl( | |||
| } | |||
| _save_provider_state(auth_store, "xai-oauth", state) | |||
| _save_auth_store(auth_store) | |||
There was a problem hiding this comment.
cleared should be set immediately after _save_auth_store(auth_store) inside the isinstance(tokens, dict) branch. If tokens is malformed, that branch performs no save but this post-block assignment still authorizes pool eviction.
GottZ
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Summary
Two PRs address the same credential-pool divergence: both retain in-memory OAuth entries when quarantine persistence raises, but #63131 reimplements the fix for the restructured current code. The current diff still treats some no-write paths as successful clears, so it does not yet fully eliminate the reported inconsistency.
Related pull requests
- #44856 [closed]
duplicate— (+195/-29) — superseded: This closed PR introduced save-failure guards for the xAI, Codex, and Nous terminal-refresh paths, but remains relevant as the original implementation and test basis superseded by the current-main reimplementation in #63131. - #63131
related— (+207/-41) — keep open with a salvage path: The diff correctly gates entry removal and persistence when_save_auth_store()raises and adds provider-specific regression tests. The contributor keep_open review identifies a remaining diff-level flaw: for xAI and Codex,cleared = Trueis assigned outside the nested token-state write guard, so malformed nested state can permit eviction without any save; move the assignment immediately after each actual_save_auth_store(auth_store)call and add the requested malformed-state tests.
Duplicates
#44856 and #63131 implement substantially the same three-provider quarantine save-failure guard; #44856 is the stale predecessor superseded by #63131.
Suggested consolidation
Keep #63131 open with a salvage path: preserve its current-main adaptation, warning-level diagnostics, full removal gating, and three save-failure regression tests, while addressing the contributor keep_open review by tying cleared = True to an actual successful save and covering malformed xAI/Codex nested state. Keep #44856 closed as superseded by #63131.
Complex graph
flowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
subgraph Dup44856 ["PRs duplicating each other"]
P44856["PR #44856 (closed)"]
P63131["PR #63131 (open)"]
end
class P44856 closed
class P63131 open
class P63131 target
click P44856 "https://github.com/NousResearch/hermes-agent/pull/44856"
click P63131 "https://github.com/NousResearch/hermes-agent/pull/63131"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).
Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 28 kB of PR diffs, 4 kB of issue/PR text, 3 kB of discussion (4 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.
Supersedes #44856 (re-cut onto current
main; the region was restructured since — notably the singleton source name changed todevice_codeand Nous now routes through_quarantine_*helpers — so this is a hand re-implementation, not a rebase).Problem
On a terminal OAuth refresh failure (xAI OAuth, Codex OAuth, Nous device-code),
agent/credential_pool.pyevicts the pool entry and persists even when the auth-store save/clear failed — diverging the in-memory pool from on-diskauth.json.Fix
Introduce a
clearedflag per provider block: setcleared = Trueonly after the save genuinely succeeds, upgrade the swalloweddebuglog towarning, and gate the entire removal block —removed_idscomputation, theself._entriesmutation, the_current_idreset, and_persist— behindif cleared:. (Gating only_persistwould be insufficient: the in-memory mutation is itself the divergence.)return Nonestays unconditional. Applied consistently to all three provider blocks.Verification
test_{nous,xai,codex}_..._keeps_entries_when_auth_store_save_fails) fail on unfixedmainwithassert [] == ['device_code'](pool wrongly emptied when the save raised) and pass with this fix; on-disk tokens asserted untouched.889533545: zero new failures vs baseline.Notes
suppress_credential_source()).