Skip to content

Implement retry-after header handling for improved throttling in fetch requests #1636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 23, 2025

Conversation

Copilot
Copy link

@Copilot Copilot AI commented Jun 23, 2025

Currently, genaiscript handles throttling situations but does not respect the retry-after header returned by services. This leads to unnecessary load on throttled services and suboptimal user experience with exponential backoff delays that may be longer than needed.

Changes Made

This PR implements proper retry-after header handling in the fetch retry logic:

1. Added parseRetryAfter() function

  • Supports both integer seconds format: retry-after: 120
  • Supports HTTP date format: retry-after: Fri, 31 Dec 1999 23:59:59 GMT
  • Handles edge cases: negative values, invalid inputs, past dates, whitespace
  • Returns null for invalid inputs to fallback to exponential backoff

2. Enhanced retry logic in createFetch()

  • Checks for retry-after header in HTTP responses during retries
  • Uses server-specified delay instead of calculated exponential backoff when available
  • Falls back to existing exponential backoff when no retry-after header is present
  • Respects maxDelay configuration to prevent excessive waits

3. Improved user transparency

  • Displays retry-after information in log messages
  • Format: retry #1 in 120.0s (retry-after: 120s) vs retry #1 in 2.0s
  • Users can now see the actual reason and duration for delays

Example Behavior

Before:

retry #1 in 2.0s 429 Too Many Requests
retry #2 in 3.0s 429 Too Many Requests  
retry #3 in 4.5s 429 Too Many Requests

After (with retry-after: 60):

retry #1 in 60.0s (retry-after: 60s) 429 Too Many Requests

Benefits

  • ✅ Reduces unnecessary load on throttled services by respecting server timing
  • ✅ Provides transparency to users about wait times and reasons
  • ✅ Maintains full backward compatibility with existing retry behavior
  • ✅ Handles common throttling scenarios (HTTP 429, 503, etc.)
  • ✅ RFC 7231 compliant retry-after header parsing

Testing

Added comprehensive unit tests covering:

  • Integer seconds parsing ("120" → 120 seconds)
  • HTTP date parsing (future dates with proper time calculation)
  • Edge cases (negative values, invalid strings, past dates)
  • Whitespace handling and input validation

Fixes #1629.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cdn.sheetjs.com
    • Triggering command: node /usr/local/bin/yarn install (dns block)

If you need me to access, download, or install something from one of these locations, you can either:


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan changed the base branch from main to dev June 23, 2025 16:18
@Copilot Copilot AI changed the title [WIP] Improve throttling handling: respect and expose retry-after value Implement retry-after header handling for improved throttling in fetch requests Jun 23, 2025
@Copilot Copilot AI requested a review from pelikhan June 23, 2025 16:19
Copilot finished work on behalf of pelikhan June 23, 2025 16:19
return delaySeconds;
}
} catch(e) {
dbg(`failed to parse retry-after header as date: %s`, errorMessage(e));
Copy link
Contributor

Choose a reason for hiding this comment

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

The catch block uses errorMessage(e), but the caught variable is named e, not error. This will cause a ReferenceError at runtime.

Suggested change
dbg(`failed to parse retry-after header as date: %s`, errorMessage(e));
dbg(`failed to parse retry-after header as date: %s`, errorMessage(e));

AI-generated content by pr-review-commit undefined_variable may be incorrect. Use reactions to eval.

if (retryAfterHeader) {
const retryAfterSeconds = parseRetryAfter(retryAfterHeader);
if (retryAfterSeconds !== null) {
delay = Math.min(maxDelay, retryAfterSeconds * 1000); // Convert to milliseconds
Copy link
Contributor

Choose a reason for hiding this comment

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

The delay is set as retryAfterSeconds * 1000, but Math.min(maxDelay, retryAfterSeconds * 1000) expects both values in milliseconds. If maxDelay is in milliseconds, this is correct, but if not, this could cause incorrect delay calculation. Please double-check the units for maxDelay and retryAfterSeconds to ensure consistency.

AI-generated content by pr-review-commit milliseconds_seconds_confusion may be incorrect. Use reactions to eval.

// Should be approximately 5 seconds (allow some tolerance)
assert(
retryAfterSeconds >= 4 && retryAfterSeconds <= 6,
`Expected ~5 seconds, got ${retryAfterSeconds}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

The test for HTTP date parsing allows a 2-second tolerance (retryAfterSeconds >= 4 && retryAfterSeconds <= 6), but timing-based tests can be flaky due to execution delays. Consider mocking the current time or using a more robust approach to avoid intermittent test failures.

AI-generated content by pr-review-commit flaky_test_due_to_timing may be incorrect. Use reactions to eval.

Copy link
Contributor

Investigator report

Context collection

  • run id: 15829867407
  • failed job
  • last successful run not found

AI Analysis

AI-generated content by gai may be incorrect. Use reactions to eval.

@pelikhan pelikhan marked this pull request as ready for review June 23, 2025 16:41
@pelikhan pelikhan merged commit a9a9a23 into dev Jun 23, 2025
10 of 14 checks passed
@pelikhan pelikhan deleted the copilot/fix-1629 branch June 23, 2025 18:38
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 throttling handling: respect and expose retry-after value
2 participants