Repository navigation
Use exponential backoff for throttled API requests - #8956
Open
isaacroldan wants to merge 3 commits into
Open
isaacroldan wants to merge 3 commits into
isaacroldan wants to merge 3 commits into
Conversation
Contributor
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationsWe found no new type declarations in this PR Existing type declarationspackages/cli-kit/dist/private/node/api.d.ts@@ -60,6 +60,8 @@ export declare function simpleRequestWithDebugLog<T extends {
* Makes a HTTP request to some API, retrying if response headers indicate a retryable error.
*
* If a request fails with a 429, the retry-after header determines a delay before an automatic retry is performed.
+ * Without a valid header, rate-limit retries use exponential backoff with jitter, capped at 8 seconds per delay.
+ * No new rate-limit retry starts after 30 seconds. A server-requested delay beyond that budget is not shortened.
*
* If unauthorizedHandler is provided, then it will be called in the case of a 401 and a retry performed. This allows
* for a token refresh for instance.
|
isaacroldan
marked this pull request as ready for review
October 20, 2026 16:35
gonzaloriestra
approved these changes
Oct 21, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 21, 2026
This branch has not been deployed
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.
WHY are these changes introduced?
Concurrent CLI operations share API quotas. Fixed retries can fail before the quota recovers, as seen in #8947.
Retry-AfterRetry-Afterasctimeused local time instead of UTC.data: {appsConnection: null}lost their retries.WHAT is this pull request doing?
Retry
THROTTLED/429 requests with exponential backoff and jitter.Retry-Afterspecifies a delay.Retry-Aftervalues.How to manually test your changes?
From a disposable app:
If throttled, retry logs should show increasing, varied delays, and deployment should resume when quota recovers. Invalid configuration should still return its original error.
Validation
96 focused API tests, changed-file lint, and CLI Kit type checks pass.
Nullable-query regressions fail before the fix for both string and typed-document requests.
Retry-Aftercases pass in UTC, Europe/Madrid, and America/Los_Angeles.Stress-test results
Tested commit:
c9285ac. Expected successes and expected errors matched the checks below.Quota unavailable for 20 seconds — eight batches of 128 concurrent operations:
66cab949Stress-test setup and checks
Timing: the same 15,000 seeded cases and 500 fake-timer cases ran in each of UTC, Europe/Madrid, and America/Los_Angeles, plus 13 boundary checks per zone. Checks covered expected successes/failures, retry caps, deadlines, changing
Retry-Afterheaders, wall-clock jumps, late timers, and final errors/request IDs. The fake-timer cases left no pending timers.Response handling: 1,456 document/error/data/status combinations and 16 cases using real GraphQL execution and the GraphQL client. Coverage included nullable and partial reads, mutation data guards, mixed errors, and invalid/ambiguous/batch documents.
Refilling quota: a separate model with capacity 32 and refill rate 32/second completed all 1,024 operations across four batches.
HTTPS: the public GraphQL client used a loopback HTTPS server, real timers, and default request behavior. Variables, tokens, and final request IDs stayed correct. There were no unhandled errors, open sockets after cleanup, or further requests during the 250 ms observation window after settlement.
Scope: quota and timing probes used simulated time; HTTPS used real local transport. No Shopify API quota, stores, or credentials were used. Deadline checks cover the outer throttle retry budget, not a total request timeout. These temporary probes are separate from committed unit tests.
Separate custom-option edge case
Forcing
defaultDelayMs: 0andlimitRetriesTo: 1100producedNaNdelays starting at retry index 1024. This was recorded as a failure and excluded from the passing counts.The sole production caller uses the default limit of ten retries and cannot reach this case.
Checklist
patch) and added a changeset