fix: retry read-only API requests the site rate-limits - #741
Merged
Merged
Conversation
XCFramework BuildThis PR's XCFramework is available for testing. Add the following to your .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/741")Built from 275cedd |
Some hosts, such as WP Cloud sites, throttle the burst of REST requests sent while the editor loads with a 429. core-data caches a failed taxonomy entity config for the session, so a throttled load leaves the Categories List and Terms List blocks unable to fetch terms until the editor reloads. The @WordPress bump in #691 exposed this: Gutenberg #78568 moved that load later in the burst, where it is throttled. Only readable 429 responses are retried, so the dev server, whose throttled CORS preflights surface as network errors, is not covered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011A5docu5NJzK5jRH8X5AnS
Clamping a long Retry-After to the cap sent both retries inside the window the server had closed, so they could only fail. The request now rejects immediately instead of after ~20s and two wasted requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y5SN26ekqCm2EmaR4CddU
Tests missed a jitter regression, the HTTP-date Retry-After form, and drift between the wrapper's copied parsing and api-fetch's own. A parity test now compares both handlers, and the no-retry tests fail on call counts rather than timing out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y5SN26ekqCm2EmaR4CddU
Keep the why for the final design and drop implementation detail that parseResponse already documents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y5SN26ekqCm2EmaR4CddU
Trunk's @wordpress/eslint-plugin 27 bump (#738) rejects `*` types, which the rate-limit retry's response parser used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A retried 429 is expected and self-healing, so it needs no attention. A 429 that exhausts its retries, or whose Retry-After exceeds the cap, can leave a failure cached for the session, and was previously logged nowhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dcalhoun
force-pushed
the
fix/retry-rate-limited-api-requests
branch
from
October 2, 2026 19:03
487df9e to
263a96b
Compare
dcalhoun
marked this pull request as ready for review
October 2, 2026 19:07
dcalhoun
requested review from
nbradbury and
oguzkocer
and removed request for
nbradbury
October 2, 2026 19:13
oguzkocer
approved these changes
Oct 3, 2026
oguzkocer
left a comment
Contributor
There was a problem hiding this comment.
I've left a minor suggestion, but otherwise looks good to me. ![]()
| // A retry sent before a longer `Retry-After` elapses would fail too. | ||
| if ( delay <= MAX_RETRY_AFTER_MS ) { | ||
| info( `Retrying ${ request } after a 429 response` ); | ||
| await wait( delay ); |
Contributor
There was a problem hiding this comment.
My understanding is that this will keep waiting for the entire delay duration even if the signal is aborted during that time, and a listener for the abort event could reject early with signal.reason.
The retry delay ignored the request's signal, so an aborted request stayed pending until the delay elapsed. It now rejects at once with the signal's reason, as fetch does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What?
Retry
api-fetchrequests with 429 status codes.Why?
The editor sends numerous requests on load. Some hosts deny these requests with 429 responses. This disrupts the editor load. In particular, blocks like Categories List will stop functioning as Gutenberg's store will cache the response as an empty collection, which prevents the block from fetching the actual data later.
How?
Wrap
@wordpress/api-fetch's default handler with retry logic. The logic targets safely repeatable requests with a backoff timing and jitter. It favors aRetry-Afterheader value if present.Note: this does not address these failures for the development server. The request in that environment are CORS and occur for preload
OPTIONSrequests. The status code is hidden from JavaScript in that context, so we cannot accurately detect the request should be retried.Testing Instructions
Tip
This was primarily reproduced via WoW (Atomic) sites where the CDN often limits the batch of requests when the editor loads.
GUTENBERG_EDITOR_URLunset)[GBK] Retrying...logs in the console for failed requestsAccessibility Testing Instructions
N/A, no presentation changes.
Screenshots or screencast
N/A, no presentation changes.
AI-generated details
What: Retries GET, HEAD, and OPTIONS requests that receive a 429, up to twice. It waits for
Retry-Afterwhen present, but fails at once if it asks for more than 10s, since retries inside that window would also fail. Otherwise it waits 0.5–1s then 2–4s with jitter.Why: Some hosts, such as WP Cloud sites, throttle the burst of REST requests sent while the editor loads. core-data caches a failed taxonomy entity config for the session, so the Categories List and Terms List blocks then never fetch terms until the editor reloads. #691 exposed this without adding requests: Gutenberg #78568 lazy-loads user pattern categories, which used to load the taxonomy config early, moving that load into the throttled part of the burst.
How: Wraps api-fetch's fetch handler rather than adding a middleware, so each network request is retried once, including
fetchAllMiddlewarepages. The wrapper requests the raw response to read its status and then parses it as api-fetch does, since api-fetch does not expose its parsing. Other statuses, network errors, offline, and aborted requests behave as before.Limitation: With the Vite dev server, the editor is cross-origin, so a throttled request fails at the CORS preflight as a network error with no status. This PR does not cover that case.
Testing:
taxonomies?context=viewreturns 429.Retrying GET …/taxonomies?context=view… after a 429 response.🤖 Generated with Claude Code
https://claude.ai/code/session_011A5docu5NJzK5jRH8X5AnS