Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[BUG]: Secondary rate limits are filed as primary ones, losing the rest of the hour

Open Beginner friendly
#3,660 2 comments 2 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
github, go, terraform

Research direction

Start at internal/ghclient/transport.go:76 and inspect the go-github-ratelimit dependency declaration. Bump the dependency past the release containing gofri/go-github-ratelimit#51, then verify that secondaryRateLimitCallback handles burst limits and that responses without retry-after are passed through as described. Done means secondary limits no longer arm the primary resource category.

Written by the indexing model from the issue text.

Description

Type: Bug Upstream
Expected Behavior

A secondary (burst) rate limit is backed off for the few seconds it asks to be waited out, and the run continues. The primary hourly quota — which has not been exhausted — stays usable.

Actual Behavior

One secondary rate limit ends the run. Every subsequent request in the same resource category is refused locally, without being sent, until the primary window resets up to an hour later:

Error: ... (http.RoundTripper).RoundTrip failed: primary rate limit reached on request to
https://api.github.com/... with category: core. wait until ... before sending more requests.

From a single terraform plan against an organisation of ~200 repositories, authenticated as a GitHub App installation (12,500 requests/hour):

288 identical errors, all timestamped within the same second, all citing the same reset ~9 minutes out

primary quota consumed across the entire run: 210 of 12,500  (1.7%)
  before   12,498 / 12,500
  after    12,288 / 12,500

A 12,500/hour primary limit cannot be exhausted by 210 requests. The run never came close to its primary budget. The 288-errors-in-one-second shape is not 288 rejections from GitHub — it is the category being armed once and every in-flight request being refused locally thereafter.

Root cause

Not in the provider. It is in github.com/gofri/go-github-ratelimit/v2 v2.0.2, wired up at internal/ghclient/transport.go:76.

GitHub sends x-ratelimit-remaining: 0 on some secondary rate limits, so the status code and that header together do not distinguish the two kinds of limit. Both of that library's limiters nevertheless decide on the header alone, from opposite directions:

  • the primary limiter arms the whole resource category for any 403/429 carrying remaining == 0, without reading the body;
  • the secondary detector returns early — commented // a primary rate limit — on that same header, before reading the body that would have identified it.

The secondary detector's deferral would be harmless if the primary limiter got it right, but github_ratelimit.New stacks the secondary limiter outside the primary one, so the primary inspects the response first, arms the category and returns an error. The secondary limiter never gets the turn the deferral assumed it would.

So a burst throttle asking for a few seconds' backoff is filed as an exhausted hourly quota, and the rest of the window is lost.

Upstream issue: gofri/go-github-ratelimit#50 — fix proposed in gofri/go-github-ratelimit#51 (makes the response body, rather than the header, decide which limit it is, on both sides).

What the provider needs to do once the fix is available

Bump github.com/gofri/go-github-ratelimit/v2 past the release carrying gofri/go-github-ratelimit#51. No provider code change — internal/ghclient/transport.go already passes the responses through both limiters correctly; they were classifying them wrongly.

Two things worth verifying with the bump, since this changes which limiter handles a response:

  1. secondaryRateLimitCallback will start firing for responses that previously fired primaryRateLimitCallback. That is the intended correction, but it means the tflog warnings change shape for anyone matching on them.
  2. A secondary limit carrying remaining: 0 and no retry-after has no usable backoff duration and is passed through to the caller as a bare 403 rather than waited out. That is a pre-existing gap in the library (the XXX in parseSecondaryLimitTime), not a new one, but it becomes reachable for this response shape. Worth a look once the bump lands.

Related but distinct: #3565 asks for the provider to wait for a primary reset rather than failing. That needs library support too — gofri/go-github-ratelimit#46, proposed in gofri/go-github-ratelimit#52 — and I have commented there with the details. The two are ordered: waiting is only safe once a primary limit actually means a primary limit, or a misclassified burst throttle turns a fail-fast into an hour-long sleep.

Terraform Version

Terraform v1.x, provider v6.13.0

GitHub Installation Type

GitHub App installation (app_auth), GitHub.com

Affected Resource(s)

All — this is in the shared HTTP transport, so any resource or data source can be the one that trips it.

Steps to Reproduce

Any plan or apply large enough to trigger GitHub's burst/secondary throttle. It needs concurrency rather than volume: ~200 repositories with several resources each was enough, well inside the primary budget.

Code of Conduct
  • I agree to follow this project's Code of Conduct
Dominant language
Go
Stars
1.2k
Forks
1.1k
Avg merge
9h 41m
Merged PRs (30d)
3

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from integrations/terraform-provider-github

All issues in integrations/terraform-provider-github

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.