Fix GitHub API Rate Limit Errors in Actions
Zusammenfassung
Pixel shares a guide to diagnosing GitHub API rate-limit errors in Actions. It compares unauthenticated limits of 60 requests per hour, GITHUB_TOKEN’s 1,000 per repository, and GitHub App limits, and explains how error messages and response headers help identify the exhausted budget.
DEV Community
GitHub API rate limit exceeded in Actions
GitHub API rate limit exceeded in Actions means the credential that step used has spent its hourly budget, and which budget that is depends entirely on what you authenticated with. GITHUB_TOKEN gets 1,000 requests an hour per repository and an unauthenticated call gets 60 an hour for the address the runner is behind, and those two numbers are far enough apart that most of these failures are a missing token rather than a busy workflow. Three budgets, three credentials, and a factor of eighty between the smallest and the largest. Most of these failures are answered by moving up one row. What this error means A step that calls the API fails with a 403 and a message naming a limit, and the shape of the message tells you which credential was spent: an address for an unauthenticated call, a user ID for a personal token, the word installation for a GitHub App. A secondary limit reads differently again, mentioning abuse detection or asking you to retry later, and it is not the same budget. The response headers are the real diagnosis, because x-ratelimit-limit says which bucket you were in without you having to guess: 60, 1,000 and 5,000 are three different stories. This page carries no recorded run. Spending a runner's unauthenticated budget is easy to do and takes an hour to give back, and the address is shared with every other job leaving the same gateway, so the block below is the sample text the Latchkey pattern library matches on, labeled as such, and every number on the page is GitHub's own, read on 2026-09-20. ```Sample log from the Latchkey pattern library GraphQL: API rate limit exceeded for user ID 12345. API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID x-ratelimit-remaining: 0
Common causes
The call was unauthenticated and nobody noticed
A curl without an authorization header, gh in a step where GH_TOKEN was never exported, or a third-party action that calls the API anonymously. It works for weeks because 60 requests an hour is enough for a quiet repository, and it fails the day traffic doubles. The address is also shared, so other jobs on the same gateway are spending the same 60.
The workflow token budget is per repository, not per job
The 1,000 an hour figure covers every job in every workflow run in that repository at once. A matrix of twenty legs that each list pull requests, on a repository with a busy merge queue, can spend it in a morning. Nothing in the failing job looks extravagant, because the spend is spread across all of them.
A step is paginating through more than it needs
Listing every workflow run, every artifact or every commit on a branch is a hundred requests before it is anything else, and the default page size makes it worse. In our experience one careless listing step accounts for most of the traffic in a repository that runs out of budget, and it is usually a step that could have asked a question instead of downloading an answer.
You hit a secondary limit and treated it as a primary one
Concurrency and content creation have their own ceilings, and a job that opens comments, creates issues or dispatches workflows in a loop can trip them with a total request count nowhere near the hourly budget. The message differs, the headers differ, and the fix is to slow down rather than to find a bigger token.
How to fix it
Authenticate every call, including the ones inside actions
- Pass
GITHUB_TOKENto every step that touches the API, including third-party actions that accept a token input. - Export
GH_TOKENfor theghCLI, which will otherwise run unauthenticated and quietly spend the 60. - Grant only the permissions the step needs, so the bigger budget does not come with a bigger blast radius.
```.github/workflows/ci.yml
permissions:
contents: read
pull-requests: read
steps:
- env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr list --limit 20 --json number,title
...

Powered by Readybot.io