Skip to content

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.

Read-only tokenPython and Node.jsTests included
Browse all 24 scripts on GitHub Download them all as a zip Field guide PDF (24 fixes, 70 KB) Follow @allanninal Every fix on this page has a tested Python and Node.js script in the repo. Free, and MIT as stated in its README.
Not GitHub Actions

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

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

GitHub APIDiagnostic

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.

Something not covered here?

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.