Skip to content

feat: handle 429s without retry-after header as error#674

Merged
JustinBeckwith merged 2 commits intoJustinBeckwith:mainfrom
miguelvelezsa:feat--handle-429s-without-retry-after-header-as-error
Oct 20, 2025
Merged

feat: handle 429s without retry-after header as error#674
JustinBeckwith merged 2 commits intoJustinBeckwith:mainfrom
miguelvelezsa:feat--handle-429s-without-retry-after-header-as-error

Conversation

@miguelvelezsa
Copy link
Contributor

@miguelvelezsa miguelvelezsa commented Jul 9, 2025

There are servers returning 429 status code but without retry-after header, so I am adding 429 status code as a retryable error.

Currently happening in several google cloud node libraries, for example: https://github.com/googleapis/gax-nodejs/actions/runs/16154578720/job/45594197234.

Request response:
headers: { 'content-length': '1474', 'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'", 'content-type': 'text/html; charset=utf-8', date: 'Tue, 08 Jul 2025 23:48:08 GMT', server: 'Varnish', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-content-type-options': 'nosniff', 'x-frame-options': 'deny', 'x-github-request-id': '', 'x-xss-protection': '1; mode=block' }, status: 429, statusText: 'Too Many Requests', request: { responseURL: 'https://github.com/googleapis/gax-nodejs/blob/chore--add-node-22-and-24-versions-in-ci-tests/LICENSE' }

I was able to recreate the issue:
image.
After the changes I don't see the error.

Note: I didn't add it in shouldRetryAfter because I don't see a max retries logic there, also since this is a special case without retry-after data I think handle it as error is better.

@miguelvelezsa
Copy link
Contributor Author

Hi @JustinBeckwith would you mind taking a look? :)

1 similar comment
@miguelvelezsa
Copy link
Contributor Author

Hi @JustinBeckwith would you mind taking a look? :)

@sofisl
Copy link

sofisl commented Aug 4, 2025

Hi @JustinBeckwith! Would you mind taking a look? This is blocking our docs generation 😢 Or let us know if another solution would be better. Thank you!

@JustinBeckwith JustinBeckwith force-pushed the feat--handle-429s-without-retry-after-header-as-error branch from 359e91b to 999b87e Compare October 20, 2025 06:13
Copy link
Owner

@JustinBeckwith JustinBeckwith left a comment

Choose a reason for hiding this comment

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

Code Review ✅

I've rebased this PR against main and fixed the test failures. All changes look good!

What I Fixed

  • Migrated from nock to undici: Updated the two new test cases to use MockAgent instead of nock, which is consistent with the rest of the test suite
  • Rebased against main: Incorporated recent bot-protection changes from #709

PR Changes Review

The core functionality looks solid:

  1. 429 without retry-after handling: Correctly treats 429s without retry-after headers as retry errors when retryErrors is enabled
  2. Test coverage: Added comprehensive tests for both retry success and retry exhaustion scenarios
  3. Documentation: Updated comments to clearly explain the new behavior

Test Results

  • ✅ All 110 tests pass
  • ✅ Linter passes (biome)
  • ✅ No type errors

The implementation correctly handles the edge case where rate-limited responses don't include retry timing information, allowing users to leverage the existing retry-error mechanism.

@JustinBeckwith JustinBeckwith force-pushed the feat--handle-429s-without-retry-after-header-as-error branch from 999b87e to 67e8c8a Compare October 20, 2025 06:15
@JustinBeckwith JustinBeckwith force-pushed the feat--handle-429s-without-retry-after-header-as-error branch from 67e8c8a to 17a9694 Compare October 20, 2025 06:16
@JustinBeckwith JustinBeckwith merged commit 9280037 into JustinBeckwith:main Oct 20, 2025
8 checks passed
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.

3 participants