fix(engine): retry transient auth errors in withRateLimitRetry#1911
Conversation
A long-running agent session holds its OAuth access token in memory. When the token rotates mid-run (Claude Max tokens have an ~8h lifetime), the next API call fails with 401 authentication_error and withRateLimitRetry re-throws it immediately — the task is marked failed and the operator is paged, even though the refreshed credentials make the very next call succeed. Observed recurring at every ~8h token boundary, hitting whatever task or heartbeat is in flight. Add isTransientAuthError (authentication_error / invalid authentication credentials / token_expired / oauth scope) with its own small retry budget: 2 retries at a flat ~5s delay (credential refresh completes within seconds, so the 30s -> 2min rate-limit backoff curve would just prolong the outage). Auth retries decrement the loop counter so they never consume rate-limit attempts, and the existing rate-limit path is byte-for-byte unchanged. Genuinely bad credentials still propagate after ~10s. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR adds transient authentication retry handling to ChangesTransient Auth Retry Handling
Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR retries short-lived OAuth token rotation failures in the rate-limit retry wrapper.
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/transient-a..." | Re-trigger Greptile |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/engine/src/__tests__/rate-limit-retry.test.ts (1)
222-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider adding a test for abort during an auth retry.
The new auth path introduces its own abort short-circuit (
authRetries >= AUTH_MAX_RETRIES || signal?.abortedinrate-limit-retry.ts), but no test exercises aborting while a transient-auth retry is pending. The existing abort test covers only the rate-limit backoff path. A small test asserting that an aborted signal rejects an in-flight auth retry would protect this new branch against regressions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/__tests__/rate-limit-retry.test.ts` around lines 222 - 242, Add a focused test in rate-limit-retry.test.ts for aborting during the auth retry path. Reuse withRateLimitRetry, vi.useFakeTimers, and an AbortController to start a transient-auth retry, abort before the 5s auth delay elapses, and assert the promise rejects instead of calling the function again. Make sure the new test covers the auth-specific short-circuit in rate-limit-retry.ts (the authRetries/AUTH_MAX_RETRIES branch) rather than the existing rate-limit backoff path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/engine/src/__tests__/rate-limit-retry.test.ts`:
- Around line 222-242: Add a focused test in rate-limit-retry.test.ts for
aborting during the auth retry path. Reuse withRateLimitRetry, vi.useFakeTimers,
and an AbortController to start a transient-auth retry, abort before the 5s auth
delay elapses, and assert the promise rejects instead of calling the function
again. Make sure the new test covers the auth-specific short-circuit in
rate-limit-retry.ts (the authRetries/AUTH_MAX_RETRIES branch) rather than the
existing rate-limit backoff path.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b60cbabf-2a73-4592-a17d-f411e7233a90
📒 Files selected for processing (2)
packages/engine/src/__tests__/rate-limit-retry.test.tspackages/engine/src/rate-limit-retry.ts
|
Yes!! Thank you |
Address review feedback on Runfusion#1911: - Greptile P1 (blocking): OAuth scope/permission failures are permanent (operator must re-authorize), so they are removed from the transient-auth classifier. A new SCOPE_ERROR_RE exclusion runs BEFORE the transient match, so scope errors wrapped in a generic {"type":"authentication_error"} envelope are also excluded instead of being retried for ~10 s. - CodeRabbit: add a test for abort during the auth-retry sleep, covering the auth-specific short-circuit (the existing abort test only exercised the rate-limit backoff path). - Add a regression test asserting scope errors (plain text, JSON-wrapped, and OAuth error codes insufficient_scope/invalid_scope) are not retried. - Add a changeset (@runfusion/fusion: patch) — engine retry behavior ships in the published CLI bundle. - Add FNXC requirement comments encoding the retry-budget invariants (separate auth budget, flat ~5 s delay, no rate-limit-attempt consumption, abort short-circuit, scope exclusion ordering). Tests: 17/17 (rate-limit-retry). tsc --noEmit clean. eslint --fix clean.
Problem
A long-running agent session holds its OAuth access token in memory. When the token rotates mid-run (Claude Max access tokens have an ~8 h lifetime), the next API call fails with
401 {"type":"error","error":{"type":"authentication_error","message":"Invalid authentication credentials"}}.withRateLimitRetryonly retries usage-limit errors and re-throws everything else immediately, so the task is marked failed and the operator is alerted — even though the credentials file has already been refreshed and the very next call would succeed.We run Fusion continuously on a server against a Claude Max subscription and see this at essentially every ~8 h token boundary: whatever task or heartbeat happens to be in flight at rotation time fails with a spurious 401, then self-heals on retry (in one case the "failed" task had actually already completed and merged 11 minutes later). The engine's
notification/oauth-*modules added in 0.55 alert on upcoming expiry, but nothing retries the in-flight call itself.Fix
Extend
withRateLimitRetrywith a transient-auth branch:isTransientAuthErrormatches"type": "authentication_error",invalid authentication credentials,token_expired/token expired, and OAuth-scope errors.Testing
rate-limit-retry.test.ts: retry-then-succeed on rotation, budget exhaustion (initial + 2), auth retries not consuming rate-limit attempts (maxRetries: 1+ auth error + 429 still succeeds), and pattern classification.vitest run src/__tests__/rate-limit-retry.test.ts— 15/15 pass;tsc --noEmitclean.🤖 Generated with Claude Code
Summary by CodeRabbit