Skip to content

fix(auth): preserve concurrently-added credentials on pool rewrite - #19608

Closed
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/credential-pool-stale-rewrite
Closed

fix(auth): preserve concurrently-added credentials on pool rewrite#19608
konsisumer wants to merge 1 commit into
NousResearch:mainfrom
konsisumer:fix/credential-pool-stale-rewrite

Conversation

@konsisumer

Copy link
Copy Markdown
Contributor

Fixes a stale-write race where a long-running Hermes process could drop credentials added by another process during credential rotation/exhaustion (issue #19566).

What changed and why

  • hermes_cli/auth.py::write_credential_pool now re-reads the on-disk pool inside the existing _auth_store_lock and merges any provider entries present on disk but missing from the caller's snapshot. This is the read-modify-write pattern requested in the issue.
  • The new removed_ids keyword lets callers signal entries they intentionally dropped, so the merge does not resurrect them.
  • agent/credential_pool.py::CredentialPool.remove_index passes the removed entry's id through removed_ids.
  • agent/credential_pool.py::load_pool records the disk ids before seeding/pruning and passes the diff as removed_ids, so stale env-seeded entries pruned at load do not come back via the merge.
  • Adds two regression tests in tests/agent/test_credential_pool.py: one reproduces the lost-update scenario from the issue (writer A loads [A,B], writer B adds C, writer A rotates and persists; final pool must still contain C), and one guards remove_index against being undone by the merge.

The rotation rewrite path (_persist) is the originally reported trigger but the same race exists for add_entry and other in-memory mutators; doing the merge inside write_credential_pool covers all callers without requiring each one to know about the on-disk state.

How to test

  • pytest tests/agent/test_credential_pool.py -q (52 passed locally, including the two new regression tests).
  • The new test_persist_preserves_concurrent_disk_only_entry fails on main (cred-C is dropped) and passes after this change.

What platforms tested on

  • macOS on darwin-arm64 (local)

Fixes #19566

@alt-glitch alt-glitch added type/bug Something isn't working 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 labels May 4, 2026
@konsisumer

Copy link
Copy Markdown
Contributor Author

Rebased onto current origin/main and resolved the auth/credential-pool conflicts within the original PR scope. The stale-write merge now also respects current-main intentional removal paths (manual DEAD pruning and OAuth quarantine), so concurrent disk-only credentials are preserved without resurrecting entries that were deliberately removed.

Verification:

  • ruff check on changed Python files: pass
  • pytest tests/agent/test_credential_pool.py -q --timeout=60: 80 passed
  • git diff --check: pass
  • python scripts/check-windows-footguns.py --diff origin/main: pass
  • Bounded full test command stopped during collection because this local environment is missing fastapi (tests/hermes_cli/test_dashboard_auth_401_reauth.py), before reaching the changed tests.

@konsisumer
konsisumer force-pushed the fix/credential-pool-stale-rewrite branch from f5855c2 to 4ee0fae Compare June 5, 2026 05:41
@liuhao1024

Copy link
Copy Markdown
Contributor

Verified: the removed_ids merge mechanism is correct.

What this fixes: When two Hermes processes load the credential pool concurrently, process A's rotation/exhaustion rewrite drops credentials that process B added between A's load_pool() and _persist(). The write_credential_pool re-read under lock + merge preserves concurrent additions while honoring intentional removals via removed_ids.

Correctness checks:

  1. All 3 terminal-auth-failure paths (loopback_pkce, device_code, nous device-code) collect removed_ids before filtering self._entries — correct ordering.
  2. remove_index passes removed_ids=[removed.id] directly to write_credential_pool (bypassing _persist) so the single removed credential is excluded from merge — clean.
  3. load_pool sanitization path computes disk_ids - new_ids as removed_ids — entries that were in the on-disk pool but dropped by normalization won't be resurrected.
  4. Both regression tests (test_persist_preserves_concurrent_disk_only_entry and test_remove_index_does_not_resurrect_via_disk_merge) verify the exact race condition and its prevention.

No issues found.

@benegessarit

Copy link
Copy Markdown
Contributor

Validated this PR against a real-world credential-loss incident (details in #19566): a long-running gateway's rotation persist erased a credential added 6 minutes earlier by hermes auth add.

  • Reproduced the field scenario on current origin/main (a8f404b): stale-snapshot persist clobbers the concurrently-added entry — confirmed broken.
  • Same repro on this PR branch: entry preserved (['bene','dbeyer7','amplify','bdavey','tmpbio']).
  • tests/agent/test_credential_pool.py on this branch: 80 passed.

This fixes a silent-data-loss path that's very visible to anyone running gateway + TUI concurrently. +1 for review.

@teknium1

Copy link
Copy Markdown
Contributor

Merged via #53896. Your commit was cherry-picked onto current main with your authorship preserved in git log (8b4c29f, der@konsi.org). Thanks for the clean read-modify-write fix and the regression tests — both landed as-is.

@teknium1 teknium1 closed this Jun 28, 2026
pai-scaffolde pushed a commit to pai-scaffolde/hermes-agent that referenced this pull request Jun 28, 2026
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
Jasper6439 pushed a commit to Jasper6439/hermes-agent that referenced this pull request Jul 5, 2026
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 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 P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenAI-Codex credential pool can drop newly added credential after stale auth.json rewrite during rotation

5 participants