anuraghazra/github-readme-stats · error · CustomError
MAX_RETRY
MAX_RETRY
Error message
Downtime due to GitHub API rate limiting
What it means
Thrown by the retryer once the recursion counter exceeds RETRIES (the number of available PAT_N tokens). The retryer increments 'retries' and recurses whenever GitHub signals RATE_LIMITED (by error type or a /rate limit/i message), returns 'Bad credentials', or returns 'account suspended'. When every token has been tried and exhausted, retries > RETRIES and this MAX_RETRY error fires. The secondary message tells users they can self-host or wait.
Source
Thrown at src/common/retryer.js:33
* @typedef {import("axios").AxiosResponse} AxiosResponse Axios response.
* @typedef {(variables: any, token: string, retriesForTests?: number) => Promise<AxiosResponse>} FetcherFunction Fetcher function.
*/
/**
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
*
* @param {FetcherFunction} fetcher The fetcher function.
* @param {any} variables Object with arguments to pass to the fetcher function.
* @param {number} retries How many times to retry.
* @returns {Promise<any>} The response from the fetcher function.
*/
const retryer = async (fetcher, variables, retries = 0) => {
if (!RETRIES) {
throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS);
}
if (retries > RETRIES) {
throw new CustomError(
"Downtime due to GitHub API rate limiting",
CustomError.MAX_RETRY,
);
}
try {
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
let response = await fetcher(
variables,
// @ts-ignore
process.env[`PAT_${retries + 1}`],
// used in tests for faking rate limit
retries,
);
// react on both type and message-based rate-limit signals.
// https://github.com/anuraghazra/github-readme-stats/issues/4425
const errors = response?.data?.errors;View on GitHub (pinned to 54a7985aee)
Solutions
- Add more tokens (PAT_2, PAT_3, ...) so the retryer has more attempts before giving up.
- Wait for the rate-limit window to reset (GitHub resets hourly) and retry.
- Self-host the instance so you control token allocation, as the secondary message suggests.
- Audit existing tokens for revocation/suspension — a single dead token is fine, but if all are dead every request walks the full list and fails.
Example fix
// before: single exhausted/revoked token // PAT_1=ghp_revoked_or_rate_limited // after: pool of valid tokens // PAT_1=ghp_xxx // PAT_2=ghp_yyy // PAT_3=ghp_zzz
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: estimate remaining GitHub rate budget before issuing the request.
// (Informational only — reduces, but cannot guarantee, MAX_RETRY.)
async function remainingRateBudget(token) {
const r = await fetch("https://api.github.com/rate_limit", {
headers: { Authorization: `token ${token}` },
});
const j = await r.json();
return j.resources.graphql.remaining;
} Try / catch
// Distinguish MAX_RETRY from other errors so you can back off rather than fail.
import { CustomError } from "../src/common/error.js";
try {
const stats = await fetchStats(username);
} catch (err) {
if (err instanceof CustomError && err.type === CustomError.MAX_RETRY) {
// schedule a retry after the GitHub rate-limit window resets
await new Promise((r) => setTimeout(r, 60_000));
return fetchStats(username);
}
throw err;
} Prevention
- Provision multiple tokens (PAT_1..PAT_N) so a single rate-limited token does not exhaust the retry budget.
- Cache responses (the project sets CACHE_TTL) so repeated identical requests do not each consume token budget.
- Self-host if you need predictable capacity rather than sharing the public instance's token pool.
When it happens
Trigger: All configured PAT_N tokens are rate-limited within one request: the retryer calls fetcher with PAT_1, sees RATE_LIMITED, recurses with PAT_2, and so on until retries > RETRIES. Also fires when every token returns 'Bad credentials' (revoked) or 'Sorry. Your account was suspended.', because those same branches recurse and burn the retry budget.
Common situations: Public Vercel instance under load where the shared token pool is exhausted; a token revoked by GitHub so each retry hits 'Bad credentials' and walks the whole list; suspended accounts; or a traffic spike against a self-hosted instance with too few tokens.
Related errors
AI-assisted analysis of anuraghazra/github-readme-stats@54a7985aee (2026-08-12).
Data as JSON: /api/errors/7f814a359bac7803.
Report an issue: GitHub.