Field notes
GitHub API fix guides
The GitHub API is generous until it is not, and the ways it stops working are quiet ones: a list that returns thirty items because nobody followed the Link header, a webhook that has been failing for a month, a secondary rate limit that answers 403 with no Retry-After. Each note here explains one such problem and gives you a script that finds it through the API.
This section is about the GitHub API as an integration surface: authentication, rate limits, pagination, webhooks, Apps and GraphQL. Workflow problems — empty secrets in fork pull requests, silent cache misses, redundant billed runs — live in GitHub Actions field notes instead.
Every script here is read only. They report what is wrong and print the repair; they never write.
GitHub API
Only the first page is read because the Link header is ignored
A repository with 340 open pull requests reports 30. The next page is advertised in the Link header, nothing errors, and page one is a truthful lie.
Per_page is unset so every list costs 3.3x more requests
Reading 3,000 issues takes 100 requests instead of 30 because nobody set per_page. The default is 30, the maximum is 100, and the difference is free.
Search returns at most 1,000 results whatever total_count says
total_count says 24,831 and page 11 returns 422 Validation Failed. The count is real; the results past the thousandth cannot be paged to at all.
The compare endpoint stops at 250 commits and says nothing
A release-notes job diffs two tags and gets exactly 250 commits back. total_commits says 812. The response is a 200 with a truncated array and no flag.
A permission error is disguised as 404 Not Found
GitHub answers 404 rather than 403 for private resources a token cannot see, so a missing scope, a missing installation and a deleted repo look identical.
Org lists silently omit SSO-enforced organizations
GET /user/orgs returns 200 and leaves out the orgs your token is not SSO-authorized for, mentioning the omission only in an X-GitHub-SSO header.
The installation covers only some repositories, silently
repository_selection is selected, the App sees 12 of 140 repositories, and every endpoint answers truthfully about those 12 and says nothing about the rest.
Resource not accessible by integration on one endpoint
One endpoint 403s for a GitHub App while everything else works. The x-accepted-github-permissions header names exactly what the endpoint wanted.
Webhook deliveries are failing and nobody reads the log
GitHub records every delivery attempt with the response it got. Your receiver has no record at all, which is why a month of 5xx goes unnoticed.
A webhook with no secret sends no signature to verify
GitHub sends X-Hub-Signature-256 only when a secret is set. Without one the header is absent, and a receiver that checks it when present checks nothing.
The hook is not subscribed to the event you are waiting for
A handler written for release or workflow_job never runs, and there is no error to find. An unsubscribed event does not fail: the delivery never exists.
The same webhook URL is registered on the org and the repo
Every event is processed twice because two independent hooks point at one URL. Dormant idempotency bugs start firing and nothing in either hook looks wrong.
Over 100 concurrent requests trips a secondary rate limit
A fan-out of parallel GETs returns 403 while x-ratelimit-remaining still shows thousands left. That combination is the whole diagnosis.
Bulk issue or comment creation exceeds 80 requests a minute
Content-generating requests get 80 a minute and 500 an hour, separately from the quota. A migration runs clean for 80 items and then 403s on every one.
The client ignores retry-after and keeps hammering the API
GitHub tells you exactly how long to wait. A fixed one-second backoff spends 60 refused requests inside that window and extends the throttle.
Polling without ETags spends full quota on unchanged data
A 304 Not Modified does not count against the rate limit. Measure x-ratelimit-used before and after an If-None-Match request and the saving is exact.
Core REST quota is exhausted and every call returns 403
GET /rate_limit is free and publishes used, limit and reset. Those three numbers say whether the drain you are running fits in the window left.
Requests go out anonymous and are capped at 60 an hour
A limit of 60 in GET /rate_limit proves the Authorization header never reached GitHub. The repair is to fail at startup rather than degrade quietly.
A hot endpoint burns 900 points a minute and gets throttled
Points and CPU time are two separate caps on one path. Measure an endpoint's response time and you can compute the request rate it will actually sustain.
Search has its own 30-per-minute bucket and drains separately
resources.search is not resources.core, and its window is 60 seconds rather than an hour. A search-per-repository loop empties it in the first minute.
Code search is billed to its own 10 a minute bucket
GET /search/code is metered by resources.code_search, not core and not search. Ten a minute is why a loop over repositories stops within seconds.
Rotating the token invalidates every cached ETag at once
ETags are scoped to the credential that minted them. When an App installation token expires each hour, a cache that returned 304s returns 200s instead.
The integration polls for events a webhook would push
An empty hook list next to a climbing core counter is the signature of a poller. Read the inventory, cost the loop in latency, and print the hook to create.
The x-poll-interval header is ignored on events endpoints
Events endpoints return x-poll-interval, the minimum seconds to wait. Polling faster returns the same cached page, and without an ETag you pay for each one.
These are the ones I keep hitting. If yours is broken in a way none of them describes, tell me on LinkedIn — it is usually how the next note gets written.