fix: Preserve session on transient refresh failures#461
Conversation
The reactive refresh path deleted the session cookie and redirected to sign-in on any failure from authenticateWithRefreshToken, including transient ones (network error, request timeout, 429, or 5xx) that survived the SDK's internal retries. During a brief outage this turned a still-valid refresh token into a forced re-authentication. Classify the failure and only clear the cookie for a terminal error. On a transient failure keep the sealed session so a later request refreshes successfully once the condition clears. Also expose an isTransient flag on TokenRefreshError for callers of refreshSession(). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Original prompt from madison.packer
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Greptile SummaryThis PR adds resilience to session refresh failures by distinguishing transient errors (network failures, 429, 5xx) from terminal ones (
Confidence Score: 5/5Safe to merge. The classification logic is correct, terminal behaviour is unchanged, and the new tests cover the key transient/terminal boundary. The core change — preserving the session cookie on transient refresh failures while still clearing it on terminal ones — is implemented correctly and is well-tested. The No files require special attention. The regex in Important Files Changed
Reviews (2): Last reviewed commit: "Follow cause chain for network errors an..." | Re-trigger Greptile |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Summary
When the access token is already invalid/expired and a refresh is required,
updateSessioncaught any error fromauthenticateWithRefreshTokenand deleted the session cookie + redirected to sign-in. That includes transient failures (network error, request timeout,429,5xx) that survive the SDK's internal retries — so a brief outage discards a still-valid refresh token and forces re-authentication (the BaseTen lockout pattern). The proactive/isExpiringpath was already resilient, but only while the current token was still valid.This classifies the failure and only tears down the session for a terminal error. On a transient failure the sealed cookie is kept, so a later request refreshes successfully once the condition clears.
Classification mirrors the SDK's own retry rules — a network failure surfaces as a
TypeErrorfrom the fetch client once retries are exhausted; transient HTTP responses carry a retryable numericstatus:refreshSession()(the explicit helper) still throwsTokenRefreshError, now carrying anisTransientflag so callers can keep the session and retry instead of signing the user out.Terminal behavior is unchanged:
invalid_grant/401/unrecognized errors still clear the cookie and redirect.Test plan
pnpm test(405 tests pass),pnpm typecheck,pnpm lintall green.session.spec.tscoverage: parametrized transient cases (429,503,408, networkTypeError) assert thewos-sessioncookie is not cleared; a terminalinvalid_grant(400) case asserts the cookie is cleared.Part of the AuthKit refresh-token DX work (server returns
429for transient refresh lock timeouts in workos/workos#66577; typed transient/terminalsession.refresh()in workos/workos-node#1663).Link to Devin session: https://app.devin.ai/sessions/fc39103abf694f90b1c92dd714a81461
Requested by: @m0tzy