{"record":{"id":"2fe501be22d9da2e","repo":"oven-sh/bun","slug":"github-api-rate-limit-exceeded-after-maxretries","errorCode":null,"errorMessage":"GitHub API rate limit exceeded after ${maxRetries} retries","messagePattern":"GitHub API rate limit exceeded after (.+?) retries","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/auto-close-duplicates.ts","lineNumber":76,"sourceCode":"  // Check rate limit headers\n  const rateLimitRemaining = response.headers.get(\"x-ratelimit-remaining\");\n  const rateLimitReset = response.headers.get(\"x-ratelimit-reset\");\n\n  if (rateLimitRemaining && parseInt(rateLimitRemaining) < 100) {\n    console.warn(`[WARNING] GitHub API rate limit low: ${rateLimitRemaining} requests remaining`);\n\n    if (parseInt(rateLimitRemaining) < 10) {\n      const resetTime = rateLimitReset ? parseInt(rateLimitReset) * 1000 : Date.now() + 60000;\n      const waitTime = Math.max(0, resetTime - Date.now());\n      console.warn(`[WARNING] Rate limit critically low, waiting ${Math.ceil(waitTime / 1000)}s until reset`);\n      await sleep(waitTime + 1000); // Add 1s buffer\n    }\n  }\n\n  // Handle rate limit errors with retry\n  if (response.status === 429 || response.status === 403) {\n    if (retryCount >= maxRetries) {\n      throw new Error(`GitHub API rate limit exceeded after ${maxRetries} retries`);\n    }\n\n    const retryAfter = response.headers.get(\"retry-after\");\n    const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : Math.min(1000 * Math.pow(2, retryCount), 32000);\n\n    console.warn(\n      `[WARNING] Rate limited (${response.status}), retry ${retryCount + 1}/${maxRetries} after ${waitTime}ms`,\n    );\n    await sleep(waitTime);\n\n    return githubRequest<T>(endpoint, token, method, body, retryCount + 1);\n  }\n\n  if (!response.ok) {\n    throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}`);\n  }\n\n  return response.json();","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/scripts/auto-close-duplicates.ts#L58-L94","documentation":"githubRequest() treats HTTP 429 and 403 as rate limiting and retries with backoff (retry-after header, else 1s * 2^n capped at 32s). After maxRetries attempts still receiving 429/403, it gives up. Important nuance: GitHub also answers 403 for permission problems, so an under-scoped token is misdiagnosed as rate limiting and will exhaust retries identically.","triggerScenarios":"Paging all issues and comments of a large repo with a PAT (60 req/h) so every request 429s; secondary rate limits from a tight comment-paging loop; a token without repo scope returning 403 on each call until retries run out.","commonSituations":"Script run locally with a classic PAT instead of the Actions GITHUB_TOKEN; loop over hundreds of issues each fetching /comments pages; bursts that trip secondary limits.","solutions":["Use the Actions runtime GITHUB_TOKEN (1000+ req/h) instead of a personal token","Honor the existing x-ratelimit-remaining < 10 pre-wait and add more spacing between page iterations","If status is 403, check token scopes/permissions first — it may be an authorization failure, not a rate limit","Wait for x-ratelimit-reset to pass and re-run the job"],"exampleFix":"// before\nconst token = process.env.MY_PAT; // 60 req/h\n\n// after\nconst token = process.env.GITHUB_TOKEN; // Actions token, 1000+ req/h\n// and distinguish permission failures from rate limits\nif (response.status === 403 && !response.headers.get('x-ratelimit-remaining')) {\n  throw new Error(`Forbidden (permissions?), not rate limited: ${await response.text()}`);\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always use the Actions GITHUB_TOKEN (or an app installation token) instead of a PAT for bulk paging","Read x-ratelimit-remaining before each page and pause near zero (the script already waits under 10)","Distinguish 403-without-ratelimit-headers (permissions) from real rate limits before retrying"],"tags":["github-api","rate-limit","retry","authentication"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}