fix(auth): retry the OAuth token exchange on transient failures - #2931
Merged
Conversation
|
View your CI Pipeline Execution ↗ for commit fc3c341
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
exchangeCodeForTokens (shared by loginWithPopup and the redirect handleLoginCallback) retries on network error / HTTP >= 500, not on 4xx. 2 retries, 1s->2s backoff. No public API change. Refs GAM-509. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bruno-imx
force-pushed
the
gam-509-retry-token-exchange
branch
from
July 28, 2026 01:53
e1c961f to
e9bcdf4
Compare
…e backoff Per-attempt AbortController timeout so a hung request becomes a retryable failure instead of an indefinite wait; full jitter on the backoff so mass 5xx retries don't synchronise into a thundering herd. Body reuse across attempts documented as deliberate/safe. Refs GAM-509. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lfportal
reviewed
Jul 28, 2026
| const errorMessage = await parseTokenErrorMessage(response); | ||
|
|
||
| // Only 5xx is transient; a 4xx (bad/expired/consumed code) is permanent. | ||
| if (response.status >= 500 && retriesLeft > 0) { |
Contributor
There was a problem hiding this comment.
Suggestion: Should we consider retrying 429 here whilst we're at it? Similar to
fetch resolves on headers, so clearing the timeout before reading the body left response.json()/text() unbounded — a server that stalls the body hung forever. Body reads now happen inside the abort window, and each attempt's outcome is classified as data so a permanent 4xx isn't retried. Adds standaloneTokenExchangeFailed / standaloneTokenExchangeRecovered telemetry so retry frequency and recovery rate are observable. Refs GAM-509. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lfportal
approved these changes
Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a bounded retry to
exchangeCodeForTokensin@imtbl/auth— the single helper behind bothloginWithPopupand the redirect-flowhandleLoginCallback— so a transient failure on the code→token exchange no longer drops an otherwise-valid login.Problem
exchangeCodeForTokensdoes a singlePOST /oauth/tokenand throws on any non-2xx, with no retry. A transient 5xx (e.g. a CloudFront502/504), a network blip, or a rate-limit therefore fails the whole login even though the authorization code is still valid and an immediate re-attempt would have succeeded. Consumers (e.g. Immutable Play) are left on a dead "Signing in…" state.Change
TokenResponseor a thrown error, just with transient blips smoothed over.Because both flows share this helper, the popup and redirect paths are both covered by the one change.
Tests
packages/auth/src/login/standalone.test.ts(exercised throughhandleLoginCallback):Local:
pnpm --filter @imtbl/auth typecheck✅ ·test✅ (4/4) ·oxlint✅Context
502/504+Forbiddenwere captured asLogin_Failedreasons during the token exchange.🤖 Generated with Claude Code