fix(auth): expired OAuth credentials survive per-provider credential discovery and silently break background operations#110678
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 18, 2026, 11:08 PM ET / July 19, 2026, 03:08 UTC. Summary PR surface: Source +50, Tests +164, Docs +3. Total +217 across 12 files. Reproducibility: no. live current-main reproduction was run in this review, but the report provides a concrete multi-profile background-discovery scenario and the branch adds focused source-level regression coverage for it. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the canonical-ordering approach once final-head validation confirms configured profile order remains authoritative and existing sole expired OAuth profiles still reach the refresh-capable downstream path. Do we have a high-confidence way to reproduce the issue? No live current-main reproduction was run in this review, but the report provides a concrete multi-profile background-discovery scenario and the branch adds focused source-level regression coverage for it. Is this the best way to solve the issue? Yes. Routing provider-level discovery through the existing canonical auth-profile ordering is narrower and more maintainable than adding a separate credential-preference implementation for background consumers. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c2a9579c5919. Label changesLabel justifications:
Evidence reviewedPR surface: Source +50, Tests +164, Docs +3. Total +217 across 12 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (7 earlier review cycles)
|
dd9b587 to
8eff96b
Compare
8eff96b to
1be607f
Compare
1be607f to
3999a9d
Compare
3999a9d to
7b38ae8
Compare
7b38ae8 to
bdd4981
Compare
|
Maintainer proof on exact head
Proof:
|
|
Merged via squash.
|
…discovery and silently break background operations (openclaw#110678) * fix(auth): reject expired OAuth credentials in provider credential discovery * test(auth): verify expired first profile is skipped for same-provider validation * fix(auth): prefer non-expired OAuth profile in per-provider credential map * fix(auth): use canonical profile order in discovery * docs(auth): document expired OAuth ordering * test(auth): use synthetic credential fixtures * test(auth): clarify resolved profile fixtures * test(auth): keep profile result names consistent * test(cli): relax ACP process deadlines under load * style(cli): format ACP process timeout --------- Co-authored-by: Peter Steinberger <[email protected]>
Related: #110488
What Problem This Solves
Fixes an issue where the per-provider credential discovery map (
resolveAgentCredentialMapFromStore) would pick the first credential in raw store order for each provider, even when a later profile was non-expired. An expired OAuth profile with a stale access token could take the one-per-provider slot ahead of a valid profile, silently breaking background operations that resolve credentials through this map (compaction safeguards, model discovery, context-engine maintenance) while identical manual operations succeeded through profile-aware resolution.Why This Change Was Made
The credential map builder now prefers a non-expired OAuth credential over an expired one when multiple profiles exist for the same provider. An expired OAuth credential is kept as a fallback — the auth resolution path can still attempt to refresh it, so dropping it entirely at the map level would leave a sole-profile provider with no credential at all. The conversion layer (
convertAuthProfileCredentialToAgent) is unchanged for OAuth: an expired-but-refreshable credential continues to pass through, matching the existing contract where the downstream refresh path owns the refresh decision.User Impact
Background operations that use the per-provider credential map now receive the freshest available OAuth profile instead of blindly picking the first one in store order. A sole expired profile still appears in the map so the refresh path can attempt recovery.
Evidence
Real runtime proof (production credential map builder)
Before (current main): the map takes the first credential in store order unconditionally. An expired OAuth ahead of a valid one wins the slot and every background operation on that provider fails.
After (this patch): the map still takes the first credential, but when a non-expired OAuth profile for the same provider appears later in the store order, it replaces the expired one:
A sole expired OAuth profile is preserved so the refresh path has a credential to work with:
Regression coverage
agent-model-discovery.auth.test.ts: 8 tests total (7 pass, 1 pre-existing keyRef fixture failure unchanged from base main).pnpm tsgo:core:test— passed.oxfmt,oxlint,git diff --checkpass.AI-assisted.