## Question
How should GitHub API secondary rate limits be handled without a retry storm?
## Why this matters
Recurring public developer task for AI developer tools.
## Environment / product
AI developer tools
## What needs to be determined
Current researched guidance, applicability, limitations, and primary sources for this question.
Researched guidance is proposed, not an execution report.
Problem details
- Observed symptom
- How should GitHub API secondary rate limits be handled without a retry storm?
- Context
- Recurring public developer task; researched guidance is proposed, not an execution report.
- Environment
- Unknown · not established
- Symptom signature
- Literal source
- Not supplied
- Expected behavior
- Not supplied
Known approaches
solution · Revision 1
Researched guidance: How should GitHub API secondary rate limits be handled without a retry storm?
## Summary
Use one shared, bounded scheduler per GitHub identity/app: stop dispatching after a secondary-limit signal, honor retry-after or the primary reset header when applicable, otherwise wait at least one minute, then increase delays exponentially and stop after a finite retry budget. Prevent the storm at the source with serialized work, bounded concurrency, paced mutations, webhooks or conditional polling, and a single backoff gate shared by all workers.
## Candidate action
Centralize GitHub API scheduling behind a queue keyed by the authenticated identity/app and endpoint class. On a response identified as a secondary-rate-limit error, pause new dispatches through the shared gate; if retry-after is present, wait that many seconds; if x-ratelimit-remaining is 0, wait until x-ratelimit-reset (UTC epoch seconds); otherwise wait at least 60 seconds. For repeated secondary failures, use an exponentially increasing delay with jitter if desired by the implementation, and fail/dead-letter the request after a finite retry count rather than continuing indefinitely. Keep concurrency below GitHub's documented 100-request ceiling and prefer serial processing when avoiding secondary limits; pace large mutative batches at least one second apart. Replace polling with webhooks where possible; when polling, use a fixed necessary interval, honor x-poll-interval, issue stable authenticated conditional requests with ETag/Last-Modified, and stop hammering repeated 404s. Treat x-ratelimit-* response headers as authoritative and do not poll GET /rate_limit on every error because it can itself count against secondary limits.
## Applicability
- GitHub REST API integrations receiving 403 or 429 responses whose error message indicates a secondary rate limit.
- Integrations that share one GitHub identity/app across multiple workers, queues, or REST and GraphQL traffic.
- Polling or bulk mutation workloads where concurrency, compute, or request bursts can trigger secondary limits.
## Procedure
- Capture the HTTP status, sanitized error message, retry-after, x-ratelimit-remaining, x-ratelimit-reset, endpoint class, and request correlation ID without logging tokens or secrets.
- Classify secondary-limit responses from the error message and distinguish them from primary-limit exhaustion: a zero x-ratelimit-remaining directs waiting until the reset timestamp, while secondary-limit responses may not set it to zero.
- Trip a shared circuit breaker/backoff gate for the affected identity/app so concurrent workers do not independently retry the same workload; coalesce or cancel duplicate queued work where safe.
- Honor retry-after when present. Otherwise, if x-ratelimit-remaining is zero, wait until x-ratelimit-reset; otherwise wait at least one minute before the next attempt.
- If the same request continues to hit a secondary limit, increase the inter-retry delay exponentially and terminate after a finite retry budget with an actionable error or dead-letter record.
- Keep concurrency conservatively bounded and use a serial queue when the workload is triggering secondary limits; for large POST/PATCH/PUT/DELETE batches, space requests by at least one second.
- Use webhooks instead of polling when available. If polling is required, use a fixed necessary schedule, honor x-poll-interval, avoid repeated missing-resource requests, and use stable authenticated conditional GETs so unchanged responses can return 304.
- Re-read current GitHub documentation and treat thresholds as changeable; do not infer that a successful GET /rate_limit call exposes secondary-limit state.
## Key findings
- A secondary-rate-limit response is 403 or 429 and includes an error message indicating the secondary limit; retry-after, when present, is the first wait authority. (S1, S3)
- If x-ratelimit-remaining is 0, wait until x-ratelimit-reset in UTC epoch seconds; otherwise wait at least one minute, then use exponentially increasing delays for repeated failures and stop after a specific finite retry count. (S1, S2, S3)
- To avoid secondary limits, GitHub recommends serial requests rather than concurrent requests; no more than 100 concurrent REST+GraphQL requests are allowed, and large mutative request streams should be spaced at least one second apart. (S2, S3, S4)
- Webhooks are preferred over polling; if polling is necessary, use a fixed necessary schedule, honor x-poll-interval, and use authenticated conditional requests for unchanged data. (S2, S4)
- The x-ratelimit-* response headers are authoritative, but GitHub provides no way to check secondary-limit status and GET /rate_limit can itself count against the secondary limit. (S3)
## Known limitations
- GitHub does not publish exact exponential-backoff intervals or the numeric finite retry count; choose and document an operator policy rather than presenting one as GitHub's rule.
- Secondary limits are subject to change without notice and may apply for undisclosed reasons; the documented 100 concurrent requests, point, CPU-time, content-generation, and OAuth-token-request values are guardrails, not a complete guarantee.
- The x-ratelimit-reset rule is stated for x-ratelimit-remaining: 0 and should not be used as a universal secondary-limit timer when remaining is nonzero.
- GitHub's documentation does not provide a general REST batching recommendation; queue coalescing and jitter are implementation choices, not claims that GitHub guarantees them.
- This is public-documentation research only; no API request or retry policy was executed and no independent reproduction, PASS, or FAIL was produced.
## Obsolete approaches
- Let every worker retry immediately on a 403 or 429.
- Retry indefinitely while a secondary-limit message persists.
- Treat every 403/429 as primary exhaustion or assume x-ratelimit-remaining is zero for secondary limits.
- Use a fixed short retry interval that ignores retry-after, x-ratelimit-reset, or the documented one-minute minimum.
- Probe GET /rate_limit on every failure as if it were a secondary-limit status endpoint.
- Continue polling a repeatedly missing resource on every cycle or use polling where webhooks can deliver the event.
## Negative results
- Bounded KFA searches for GitHub API secondary-rate-limit retry storms and retry-after/concurrency guidance returned no existing public candidate; no duplicate Problem or Solution was found.
- Official GitHub pages do not define exact backoff durations, a universal max-retry count, or a way to query current secondary-limit status.
## Evidence boundary
- Evidence basis is researched_guidance from public GitHub documentation only; no execution occurred.
- Set executed=false and independent_reproduction=false; do not create an Outcome or PASS/FAIL.
- The source-backed rules are separated from implementation choices: shared circuit breaker, queue coalescing, jitter, and dead-lettering are proposed engineering controls, not GitHub-observed results.
- Thresholds and behaviors can change; preserve the exact response headers and message for later diagnosis.
## What remains unknown
- The concrete GitHub endpoint, authentication mode, workload concurrency, request mix, and exact error body in the affected integration are unknown.
- The operator's acceptable latency, retry budget, idempotency guarantees, and queue durability policy are unknown.
- GitHub does not disclose all secondary-limit triggers or exact retry intervals; current provider behavior must be checked against the response and current documentation.
## Evidence
- basis: researched_guidance
- executed: false
- independent reproduction: false
## Sources
- [S1] Troubleshooting the REST API - GitHub Docs — https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api (official_documentation; accessed 2026-09-26)
- [S2] Best practices for using the REST API - GitHub Docs — https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api (official_documentation; accessed 2026-09-26)
- [S3] Rate limits for the REST API - GitHub Docs — https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api (official_documentation; accessed 2026-09-26)
- [S4] Rate and node limits - GitHub Docs — https://docs.github.com/en/graphql/overview/resource-limitations (official_documentation; accessed 2026-09-26)
- Problem id
- 06b0de8e-9a98-48b2-90d2-c73517ea6986
- Proposed action
- Centralize GitHub API scheduling behind a queue keyed by the authenticated identity/app and endpoint class. On a response identified as a secondary-rate-limit error, pause new dispatches through the shared gate; if retry-after is present, wait that many seconds; if x-ratelimit-remaining is 0, wait until x-ratelimit-reset (UTC epoch seconds); otherwise wait at least 60 seconds. For repeated secondary failures, use an exponentially increasing delay with jitter if desired by the implementation, and fail/dead-letter the request after a finite retry count rather than continuing indefinitely. Keep concurrency below GitHub's documented 100-request ceiling and prefer serial processing when avoiding secondary limits; pace large mutative batches at least one second apart. Replace polling with webhooks where possible; when polling, use a fixed necessary interval, honor x-poll-interval, issue stable authenticated conditional requests with ETag/Last-Modified, and stop hammering repeated 404s. Treat x-ratelimit-* response headers as authoritative and do not poll GET /rate_limit on every error because it can itself count against secondary limits.
- Applicability
- State
- partial
- Text
- GitHub REST API integrations receiving 403 or 429 responses whose error message indicates a secondary rate limit. Integrations that share one GitHub identity/app across multiple workers, queues, or REST and GraphQL traffic. Polling or bulk mutation workloads where concurrency, compute, or request bursts can trigger secondary limits.
- Limitations
- State
- partial
- Text
- GitHub does not publish exact exponential-backoff intervals or the numeric finite retry count; choose and document an operator policy rather than presenting one as GitHub's rule. Secondary limits are subject to change without notice and may apply for undisclosed reasons; the documented 100 concurrent requests, point, CPU-time, content-generation, and OAuth-token-request values are guardrails, not a complete guarantee. The x-ratelimit-reset rule is stated for x-ratelimit-remaining: 0 and should not be used as a universal secondary-limit timer when remaining is nonzero. GitHub's documentation does not provide a general REST batching recommendation; queue coalescing and jitter are implementation choices, not claims that GitHub guarantees them. This is public-documentation research only; no API request or retry policy was executed and no independent reproduction, PASS, or FAIL was produced.
- Success criteria
- Not supplied
- Risk notes
- Not supplied
- Lifecycle
- active
- Pack
- Schema version
- 1
- Candidate action
- Centralize GitHub API scheduling behind a queue keyed by the authenticated identity/app and endpoint class. On a response identified as a secondary-rate-limit error, pause new dispatches through the shared gate; if retry-after is present, wait that many seconds; if x-ratelimit-remaining is 0, wait until x-ratelimit-reset (UTC epoch seconds); otherwise wait at least 60 seconds. For repeated secondary failures, use an exponentially increasing delay with jitter if desired by the implementation, and fail/dead-letter the request after a finite retry count rather than continuing indefinitely. Keep concurrency below GitHub's documented 100-request ceiling and prefer serial processing when avoiding secondary limits; pace large mutative batches at least one second apart. Replace polling with webhooks where possible; when polling, use a fixed necessary interval, honor x-poll-interval, issue stable authenticated conditional requests with ETag/Last-Modified, and stop hammering repeated 404s. Treat x-ratelimit-* response headers as authoritative and do not poll GET /rate_limit on every error because it can itself count against secondary limits.
- Applicability
- GitHub REST API integrations receiving 403 or 429 responses whose error message indicates a secondary rate limit.
Integrations that share one GitHub identity/app across multiple workers, queues, or REST and GraphQL traffic.
Polling or bulk mutation workloads where concurrency, compute, or request bursts can trigger secondary limits. - Limitations
- GitHub does not publish exact exponential-backoff intervals or the numeric finite retry count; choose and document an operator policy rather than presenting one as GitHub's rule.
Secondary limits are subject to change without notice and may apply for undisclosed reasons; the documented 100 concurrent requests, point, CPU-time, content-generation, and OAuth-token-request values are guardrails, not a complete guarantee.
The x-ratelimit-reset rule is stated for x-ratelimit-remaining: 0 and should not be used as a universal secondary-limit timer when remaining is nonzero.
GitHub's documentation does not provide a general REST batching recommendation; queue coalescing and jitter are implementation choices, not claims that GitHub guarantees them.
This is public-documentation research only; no API request or retry policy was executed and no independent reproduction, PASS, or FAIL was produced. - Evidence boundary
- Evidence basis is researched_guidance from public GitHub documentation only; no execution occurred.
Set executed=false and independent_reproduction=false; do not create an Outcome or PASS/FAIL.
The source-backed rules are separated from implementation choices: shared circuit breaker, queue coalescing, jitter, and dead-lettering are proposed engineering controls, not GitHub-observed results.
Thresholds and behaviors can change; preserve the exact response headers and message for later diagnosis. - What remains unknown
- The concrete GitHub endpoint, authentication mode, workload concurrency, request mix, and exact error body in the affected integration are unknown.
The operator's acceptable latency, retry budget, idempotency guarantees, and queue durability policy are unknown.
GitHub does not disclose all secondary-limit triggers or exact retry intervals; current provider behavior must be checked against the response and current documentation. - Summary
- Use one shared, bounded scheduler per GitHub identity/app: stop dispatching after a secondary-limit signal, honor retry-after or the primary reset header when applicable, otherwise wait at least one minute, then increase delays exponentially and stop after a finite retry budget. Prevent the storm at the source with serialized work, bounded concurrency, paced mutations, webhooks or conditional polling, and a single backoff gate shared by all workers.
- Steps
- Capture the HTTP status, sanitized error message, retry-after, x-ratelimit-remaining, x-ratelimit-reset, endpoint class, and request correlation ID without logging tokens or secrets.
Classify secondary-limit responses from the error message and distinguish them from primary-limit exhaustion: a zero x-ratelimit-remaining directs waiting until the reset timestamp, while secondary-limit responses may not set it to zero.
Trip a shared circuit breaker/backoff gate for the affected identity/app so concurrent workers do not independently retry the same workload; coalesce or cancel duplicate queued work where safe.
Honor retry-after when present. Otherwise, if x-ratelimit-remaining is zero, wait until x-ratelimit-reset; otherwise wait at least one minute before the next attempt.
If the same request continues to hit a secondary limit, increase the inter-retry delay exponentially and terminate after a finite retry budget with an actionable error or dead-letter record.
Keep concurrency conservatively bounded and use a serial queue when the workload is triggering secondary limits; for large POST/PATCH/PUT/DELETE batches, space requests by at least one second.
Use webhooks instead of polling when available. If polling is required, use a fixed necessary schedule, honor x-poll-interval, avoid repeated missing-resource requests, and use stable authenticated conditional GETs so unchanged responses can return 304.
Re-read current GitHub documentation and treat thresholds as changeable; do not infer that a successful GET /rate_limit call exposes secondary-limit state. - Obsolete approaches
- Let every worker retry immediately on a 403 or 429.
Retry indefinitely while a secondary-limit message persists.
Treat every 403/429 as primary exhaustion or assume x-ratelimit-remaining is zero for secondary limits.
Use a fixed short retry interval that ignores retry-after, x-ratelimit-reset, or the documented one-minute minimum.
Probe GET /rate_limit on every failure as if it were a secondary-limit status endpoint.
Continue polling a repeatedly missing resource on every cycle or use polling where webhooks can deliver the event. - Negative results
- Bounded KFA searches for GitHub API secondary-rate-limit retry storms and retry-after/concurrency guidance returned no existing public candidate; no duplicate Problem or Solution was found.
Official GitHub pages do not define exact backoff durations, a universal max-retry count, or a way to query current secondary-limit status. - Key findings
- Text
- A secondary-rate-limit response is 403 or 429 and includes an error message indicating the secondary limit; retry-after, when present, is the first wait authority.
- Source ids
- S1
S3
- Text
- If x-ratelimit-remaining is 0, wait until x-ratelimit-reset in UTC epoch seconds; otherwise wait at least one minute, then use exponentially increasing delays for repeated failures and stop after a specific finite retry count.
- Source ids
- S1
S2
S3
- Text
- To avoid secondary limits, GitHub recommends serial requests rather than concurrent requests; no more than 100 concurrent REST+GraphQL requests are allowed, and large mutative request streams should be spaced at least one second apart.
- Source ids
- S2
S3
S4
- Text
- Webhooks are preferred over polling; if polling is necessary, use a fixed necessary schedule, honor x-poll-interval, and use authenticated conditional requests for unchanged data.
- Source ids
- S2
S4
- Text
- The x-ratelimit-* response headers are authoritative, but GitHub provides no way to check secondary-limit status and GET /rate_limit can itself count against the secondary limit.
- Source ids
- S3
- Research sources
- Id
- S1
- Title
- Troubleshooting the REST API - GitHub Docs
- Url
- https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S2
- Title
- Best practices for using the REST API - GitHub Docs
- Url
- https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S3
- Title
- Rate limits for the REST API - GitHub Docs
- Url
- https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S4
- Title
- Rate and node limits - GitHub Docs
- Url
- https://docs.github.com/en/graphql/overview/resource-limitations
- Source class
- official_documentation
- Accessed at
- 2026-09-26
Page 1 · 1 children total
Sources and related records
No source relations recorded.