Skip to content

feat: add support for handling Retry-After header#267

Merged
SoulPancake merged 15 commits into
mainfrom
feat/support-retry-after-header
Oct 23, 2025
Merged

feat: add support for handling Retry-After header#267
SoulPancake merged 15 commits into
mainfrom
feat/support-retry-after-header

Conversation

@SoulPancake

@SoulPancake SoulPancake commented Oct 9, 2025

Copy link
Copy Markdown
Member

Description

For #208

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Added support for Retry-After headers to control retry timing on rate-limited or server errors.
    • Improved retry logic with exponential backoff for network failures and retryable errors, with sensible maximum delays.
  • Documentation

    • Updated changelog to note Retry-After handling.
  • Tests

    • Expanded test coverage for Retry-After scenarios and exponential backoff on 5xx errors.
    • Enhanced test utilities to allow specifying response headers in mocks.

@coderabbitai

coderabbitai Bot commented Oct 9, 2025

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title precisely describes the primary feature addition—support for handling the Retry-After header—using concise and clear wording that matches the main change in the pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented Oct 9, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.88%. Comparing base (3fe33ac) to head (d2fe60f).
⚠️ Report is 55 commits behind head on main.

Files with missing lines Patch % Lines
common.ts 96.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #267      +/-   ##
==========================================
+ Coverage   88.43%   88.88%   +0.45%     
==========================================
  Files          23       23              
  Lines        1219     1260      +41     
  Branches      198      230      +32     
==========================================
+ Hits         1078     1120      +42     
+ Misses         85       84       -1     
  Partials       56       56              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SoulPancake

Copy link
Copy Markdown
Member Author

This is generated using the existing sdk-generator PR openfga/sdk-generator#504

@SoulPancake SoulPancake changed the title feat: retry after header feat: add support for handling Retry-After header Oct 9, 2025
@SoulPancake
SoulPancake marked this pull request as ready for review October 13, 2025 06:20
@SoulPancake
SoulPancake requested a review from a team as a code owner October 13, 2025 06:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
common.ts (5)

145-147: Allow Retry-After: 0 and handle small negative skew

RFC allows Retry-After of 0 (immediate retry). Also clamp small negative delays due to clock skew to 0.

Apply this diff:

-function isValidRetryDelay(delayMs: number): boolean {
-  return delayMs > 0 && delayMs <= 1800 * 1000;
-}
+function isValidRetryDelay(delayMs: number): boolean {
+  return delayMs >= 0 && delayMs <= 1800 * 1000;
+}
-    const dateValue = new Date(retryAfterValue);
-    const now = new Date();
-    const delayMs = dateValue.getTime() - now.getTime();
+    const dateValue = new Date(retryAfterValue);
+    const now = new Date();
+    const delayMs = Math.max(0, dateValue.getTime() - now.getTime());
 
-    if (isValidRetryDelay(delayMs)) {
+    if (isValidRetryDelay(delayMs)) {
       return delayMs;
     }

Also applies to: 172-179


139-144: Avoid 0ms backoff when minWaitInMs is unset (burst risk)

With minWaitInMs=0, randomTime returns 0ms. Use a small floor to prevent tight loops.

Apply this diff:

-function randomTime(loopCount: number, minWaitInMs: number): number {
-  const min = Math.ceil(2 ** loopCount * minWaitInMs);
-  const max = Math.ceil(2 ** (loopCount + 1) * minWaitInMs);
+function randomTime(loopCount: number, minWaitInMs: number): number {
+  const base = Math.max(minWaitInMs, 50); // 50ms floor
+  const min = Math.ceil(2 ** loopCount * base);
+  const max = Math.ceil(2 ** (loopCount + 1) * base);
   const calculatedTime = Math.floor(Math.random() * (max - min) + min);
   return Math.min(calculatedTime, 120 * 1000);
 }

276-279: Default retry params can yield 0ms backoff

If configuration.retryParams is undefined and options omit retryParams, minWaitInMs becomes 0. Ensure sane defaults when maxRetry > 0.

Apply this diff:

-  const retryParams = axiosArgs.options?.retryParams ? axiosArgs.options?.retryParams : configuration.retryParams;
-  const maxRetry:number = retryParams ? retryParams.maxRetry : 0;
-  const minWaitInMs:number = retryParams ? retryParams.minWaitInMs : 0;
+  const retryParams = axiosArgs.options?.retryParams ?? configuration.retryParams;
+  const maxRetry: number = retryParams?.maxRetry ?? 0;
+  const minWaitInMs: number = maxRetry > 0 ? Math.max(50, retryParams?.minWaitInMs ?? 200) : 0;

230-266: Optional: include 408 (Request Timeout) in retryable statuses

408 is commonly safe to retry with backoff.

Apply this diff:

-      } else if (status === 429 || (status >= 500 && status !== 501)) {
+      } else if (status === 408 || status === 429 || (status >= 500 && status !== 501)) {

200-201: Optional: narrow return type (never returns undefined after fixes)

Control flow always returns a WrappedAxiosResponse or throws. Simplify the signature.

Apply this diff:

-): Promise<WrappedAxiosResponse<R> | undefined> {
+): Promise<WrappedAxiosResponse<R>> {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a61ccf6 and 7f24c99.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • CHANGELOG.md (1 hunks)
  • common.ts (7 hunks)
  • tests/helpers/nocks.ts (1 hunks)
  • tests/index.test.ts (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/index.test.ts (3)
tests/helpers/default-config.ts (2)
  • OPENFGA_API_TOKEN_ISSUER (20-20)
  • baseConfig (54-67)
configuration.ts (1)
  • GetDefaultRetryParams (48-53)
api.ts (1)
  • OpenFgaApi (1284-1508)
common.ts (2)
base.ts (1)
  • RequestArgs (29-32)
errors.ts (5)
  • FgaError (27-40)
  • FgaApiValidationError (120-134)
  • FgaApiNotFoundError (142-155)
  • FgaApiRateLimitExceededError (165-179)
  • FgaApiInternalError (187-203)
🔇 Additional comments (1)
common.ts (1)

250-263: Good: honors Retry-After header with sensible fallback

Parsing Retry-After and falling back to capped exponential backoff is correct and improves resilience.

Consider adding tests for:

  • Retry-After: "0", very large value (clamped), HTTP-date in past (treated as 0), invalid string (fallback to backoff).
  • Network error retry path.

Comment thread common.ts Outdated
Comment thread common.ts Outdated

@rhamzeh rhamzeh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check what's happening with the spaces?

Also can you add tests for:

  1. errors on token exchange
  2. invalid or missing retry-after header
  3. retry after header with time > max time
  4. retry after header with 0 time
  5. No retries for 501 errors

Comment thread tests/index.test.ts Outdated
Comment thread common.ts Outdated
Comment thread common.ts Outdated
Comment thread common.ts Outdated
@SoulPancake
SoulPancake requested a review from rhamzeh October 20, 2025 14:36
Comment thread tests/index.test.ts Outdated
Comment thread common.ts Outdated
@rhamzeh

rhamzeh commented Oct 20, 2025

Copy link
Copy Markdown
Member

Side-note: can you make sure to run npm run lint:fix?

@rhamzeh

rhamzeh commented Oct 20, 2025

Copy link
Copy Markdown
Member

@coderabbitai can you create an issue to make sure npm run lint is run and CI fails otherwise?

The deliverables for that tickets:

  1. CI/CD check lint
  2. (optional/nice to have) githook to fail the commit if lint fails

@coderabbitai

coderabbitai Bot commented Oct 20, 2025

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Create a GitHub issue for adding lint checks to CI/CD
gh issue create \
  --title "Add lint checks to CI/CD pipeline" \
  --body "## Description

Ensure that \`npm run lint\` is executed in the CI/CD pipeline and that the build fails if linting errors are detected.

## Deliverables

1. **CI/CD check lint**: Add a step to run \`npm run lint\` in the CI/CD pipeline that fails the build if linting errors are found
2. **(Optional/Nice to have)**: Add a git hook to fail commits locally if lint fails

## Context

This issue was created as a follow-up action from PR #267.

**Requested by**: @rhamzeh  
**Related PR**: https://github.com/openfga/js-sdk/pull/267  
**Comment**: https://github.com/openfga/js-sdk/pull/267#issuecomment" \
  --assignee rhamzeh

Length of output: 44


@rhamzeh I've created issue #275 for adding lint checks to the CI/CD pipeline. The issue includes both deliverables you specified:

  1. CI/CD check lint
  2. (Optional/nice to have) githook to fail the commit if lint fails

The issue has been assigned to you and includes a reference back to this PR and your comment.

🔗 #275

Copilot AI review requested due to automatic review settings October 20, 2025 16:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces support for handling the Retry-After HTTP header to improve retry logic when the API responds with rate-limiting (429) or server errors (5xx). When the Retry-After header is present, the SDK uses the server-specified delay instead of exponential backoff. If the header is missing, invalid, or exceeds configured limits, the SDK falls back to exponential backoff with a capped maximum wait time.

Key Changes

  • Added parsing and validation logic for Retry-After headers (both integer seconds and HTTP-date formats)
  • Introduced maximum retry delay caps (120 seconds for exponential backoff, 30 minutes for Retry-After)
  • Excluded 501 errors from retry logic as they are non-retryable
  • Expanded test coverage for Retry-After scenarios, exponential backoff behavior, and edge cases

Reviewed Changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

File Description
common.ts Added Retry-After header parsing, validation logic, refactored retry error handling, and introduced delay caps
tests/index.test.ts Added comprehensive tests for Retry-After handling, exponential backoff, and non-retryable errors
tests/helpers/nocks.ts Updated tokenExchange mock to accept custom headers for testing retry scenarios
CHANGELOG.md Documented the new Retry-After header support feature

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread common.ts Outdated
Comment thread common.ts Outdated
Comment thread common.ts Outdated
Comment thread common.ts
Comment thread common.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

common.ts:1

  • The test verifies that elapsed time is less than 2500ms but doesn't verify the lower bound (should be at least ~2000ms to confirm the Retry-After header was respected). Add an assertion like expect(elapsedTime).toBeGreaterThanOrEqual(1900).
/**

common.ts:1

  • The test for 'valid Retry-After header (integer seconds)' verifies the result but doesn't verify that the delay was actually respected (lines 945-949). Add assertions to check that elapsed time is approximately 2 seconds, similar to the HTTP date test at line 987.
/**

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread tests/index.test.ts Outdated
Comment thread tests/index.test.ts Outdated
@SoulPancake

Copy link
Copy Markdown
Member Author

Running npm run lint:fix doesn't catch any errors btw
image

@dyeam0 dyeam0 linked an issue Oct 22, 2025 that may be closed by this pull request
1 task
@SoulPancake
SoulPancake enabled auto-merge October 22, 2025 16:46
@SoulPancake
SoulPancake disabled auto-merge October 22, 2025 17:19
@SoulPancake
SoulPancake added this pull request to the merge queue Oct 23, 2025
Merged via the queue into main with commit 351b3d0 Oct 23, 2025
21 checks passed
@SoulPancake
SoulPancake deleted the feat/support-retry-after-header branch October 23, 2025 16:01
@openfga-releaser-bot openfga-releaser-bot Bot mentioned this pull request Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the retry strategy

5 participants