{"record":{"id":"869829824a985ef1","repo":"midudev/autoskills","slug":"github-rate-limit-exceeded-resetsuffix-set-github-token-or","errorCode":null,"errorMessage":"GitHub rate limit exceeded${resetSuffix}. Set GITHUB_TOKEN or GH_TOKEN to increase the limit.","messagePattern":"GitHub rate limit exceeded(.+?)\\. Set GITHUB_TOKEN or GH_TOKEN to increase the limit\\.","errorType":"http","errorClass":"Error","httpStatus":403,"severity":"error","filePath":"packages/autoskills/installer.ts","lineNumber":301,"sourceCode":"\n  const expected = entry.sha256[rel] || entry.sha256[normalizedRel];\n  if (!expected) {\n    throw new Error(`no recorded hash for ${normalizedRel}`);\n  }\n\n  const fetchFile = opts.fetchImpl || fetch;\n  const errors = [];\n  for (const baseUrl of getRegistryRawBaseUrls(opts)) {\n    const url = `${baseUrl}/${encodeRawPath(skillName, normalizedRel)}`;\n    opts.onTrace?.(`GET ${url}`);\n    const res = await fetchFile(url, {\n      headers: githubDownloadHeaders(url),\n    });\n    if (!res.ok) {\n      const resetAt = Number(res.headers.get(\"x-ratelimit-reset\") || 0) * 1000;\n      const resetSuffix = resetAt ? ` (resets ${new Date(resetAt).toISOString()})` : \"\";\n      if (res.status === 403 && res.headers.get(\"x-ratelimit-remaining\") === \"0\") {\n        throw new Error(\n          `GitHub rate limit exceeded${resetSuffix}. Set GITHUB_TOKEN or GH_TOKEN to increase the limit.`,\n        );\n      }\n      errors.push(`${res.status} ${res.statusText} from ${baseUrl}`);\n      opts.onTrace?.(`miss ${normalizedRel}: ${res.status} ${res.statusText} from ${baseUrl}`);\n      continue;\n    }\n\n    const buf = Buffer.from(await res.arrayBuffer());\n    const actual = sha256Buffer(buf);\n    if (actual !== expected) {\n      errors.push(`hash mismatch from ${baseUrl}`);\n      opts.onTrace?.(`hash mismatch for ${normalizedRel} from ${baseUrl}`);\n      continue;\n    }\n    opts.onTrace?.(`downloaded ${normalizedRel} from ${url}`);\n    return { buf, url };\n  }","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/midudev/autoskills/blob/0ec725320d2137253ab2e68e7ba8a072148e741a/packages/autoskills/installer.ts#L283-L319","documentation":"When fetching a skill file from GitHub raw, a 403 response with the x-ratelimit-remaining header equal to 0 means GitHub's API rate limit is exhausted for the current (unauthenticated or token-bound) identity. The installer surfaces this explicitly, including the reset timestamp, because retrying immediately cannot succeed and the fix is authentication or waiting.","triggerScenarios":"downloadRegistryFile iterates its candidate base URLs and the fetch returns 403 + x-ratelimit-remaining: 0 — typically during bulk skill installs from an unauthenticated IP, or with a GITHUB_TOKEN whose rate limit is spent.","commonSituations":"CI runners on shared IPs installing many skills without a token; hitting the 60 req/hour unauthenticated limit on raw.githubusercontent/api endpoints; a leaked/revoked token causing unauthenticated classification.","solutions":["Set the GITHUB_TOKEN (or GH_TOKEN) environment variable with a valid personal access token to raise the rate limit.","Wait until the reset time reported in the error message, then retry the install.","If a token is already set, verify it is valid and not expired/revoked.","Reduce request volume (install fewer skills at once) or use a custom registryBaseUrl that is not GitHub rate limited."],"exampleFix":"// before (CI)\n- run: pnpm autoskills install skill-a skill-b ...\n\n// after\n- run: pnpm autoskills install skill-a skill-b ...\n  env:\n    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}","handlingStrategy":"retry","validationCode":"// check budget before a bulk install\nconst res = await fetch(\"https://api.github.com/rate_limit\", {\n  headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN ?? \"\"}`.trim() || undefined },\n});\nconst { remaining } = (await res.json()).resources.core;\nif (remaining < estimatedRequests) throw new Error(`GitHub rate budget too low: ${remaining} left`);","typeGuard":null,"tryCatchPattern":"try {\n  await downloadRegistryEntry(name, entry, dest);\n} catch (e) {\n  const m = e.message.match(/GitHub rate limit exceeded \\(resets (.+?)\\)/);\n  if (m) {\n    const waitMs = Math.max(0, new Date(m[1]).getTime() - Date.now()) + 1000;\n    await sleep(waitMs);\n    return downloadRegistryEntry(name, entry, dest); // retry once after reset\n  } else throw e;\n}","preventionTips":["Always set GITHUB_TOKEN/GH_TOKEN in CI and local scripts that touch GitHub.","For bulk operations, check GET /rate_limit first and throttle accordingly.","Rotate tokens before expiry so a stale token never silently downgrades you to anonymous limits.","Batch installs and avoid re-running full syncs unnecessarily."],"tags":["github","rate-limit","network","authentication"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"0ec725320d2137253ab2e68e7ba8a072148e741a","analyzedAt":"2026-09-15T14:12:31.090Z","contentChangedAt":"2026-09-15T14:12:31.090Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}