fix(auth): recover from concurrent session refreshes#213
Conversation
🦋 Changeset detectedLatest commit: 9573196 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
25bf8c6 to
708db0c
Compare
708db0c to
6ecad3e
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughIntroduces Estimated code review effort🎯 4 (Complex) | ⏱️ ~35 minutes Detailed explanation
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Comment |
6ecad3e to
38431d4
Compare
rafa-thayto
left a comment
There was a problem hiding this comment.
LGTM with two suggestions below. The approach is solid and well-scoped. The fingerprint + retry strategy is a clean way to handle this race.
Prevent unbounded recursion when a newer stored session is itself expired, and ensure recovery failures still fall through to the delete + session_expired path so the user gets the friendly re-auth prompt instead of a raw error.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/lib/credential-store.ts`:
- Around line 31-32: The timed-polling invalid_grant fallback currently clears
the credential store unconditionally after the retry budget
(INVALID_GRANT_RETRY_DELAYS_MS) and can delete a freshly written session; change
the fallback to be non-destructive by verifying the store still contains the
original fingerprint before deleting or by avoiding deletion entirely and
instead marking the local attempt as failed. Concretely: capture the original
fingerprint value when you start the retry loop, then before calling the code
that clears the store (the branch that deletes credentials on invalid_grant),
re-read the credential store and compare its current fingerprint to the captured
original; only perform the destructive clear if they match, otherwise skip
deletion (or set a non-destructive failure flag/log). Ensure this check is
applied to the invalid_grant branch that references
INVALID_GRANT_RETRY_DELAYS_MS so the race is eliminated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c9ff5427-60ad-42a6-a67d-b3f4650d0923
📒 Files selected for processing (1)
packages/cli-core/src/lib/credential-store.ts
Rename recoverFromInvalidGrant to awaitConcurrentRefresh and add a JSDoc covering the race window, polling budget, and detection rationale. Drop the sessionFingerprint helper in favor of comparing refresh tokens directly, since the OAuth server rotates them on every successful exchange. Update the test description to make the concurrent-refresh framing explicit.
Summary
Follow-up to #205. When two separate
clerkprocesses start from the same expired OAuth session, they can both race intorefreshStoredSession(). If one process refreshes successfully and rotates the refresh token, the loser seesinvalid_grantand, in the current implementation, deletes the credentials that the winner just rewrote.This PR keeps the existing refresh flow but hardens the
invalid_grantpath so a losing process re-reads the credential store before treating the session as dead. If another process already stored a newer session, the loser reuses that session instead of deleting credentials and forcing a re-auth.Fix
invalid_grantincredential-store.ts.25ms,50ms,100ms) before deleting anything. A different refresh token implies a sibling process already rotated the session.Test plan
bunx tsc -p packages/cli-core/tsconfig.json --noEmitbun test packages/cli-core/src/lib/credential-store.test.tsbun test packages/cli-core/src/lib/plapi.test.ts packages/cli-core/src/lib/token-exchange.test.ts packages/cli-core/src/commands/auth/login.test.ts packages/cli-core/src/commands/doctor/doctor.test.ts packages/cli-core/src/commands/whoami/index.test.ts