{"record":{"id":"9cc3160f22e1fc95","repo":"can1357/oh-my-pi","slug":"github-api-rate-limit-exceeded-while-fetching-rele","errorCode":null,"errorMessage":"GitHub API rate limit exceeded while fetching release metadata; retry later or set GITHUB_TOKEN or GH_TOKEN","messagePattern":"GitHub API rate limit exceeded while fetching release metadata; retry later or set GITHUB_TOKEN or GH_TOKEN","errorType":"exception","errorClass":"Error","httpStatus":403,"severity":"error","filePath":"packages/coding-agent/src/cli/update-cli.ts","lineNumber":272,"sourceCode":"\t\t\"X-GitHub-Api-Version\": \"2022-11-28\",\n\t};\n\tif (githubToken) headers.Authorization = `Bearer ${githubToken}`;\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetchImpl(`${GITHUB_API}/repos/${REPO}/releases/tags/${encodeURIComponent(tag)}`, {\n\t\t\theaders,\n\t\t\tsignal: withTimeoutSignal(RELEASE_METADATA_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tif (isTimeoutError(err)) {\n\t\t\tthrow new Error(\"Timed out fetching GitHub release metadata after 30s\", { cause: err });\n\t\t}\n\t\tif (isUnsupportedProxyError(err)) throw new Error(unsupportedProxyMessage(), { cause: err });\n\t\tthrow err;\n\t}\n\tif ((response.status === 403 && !githubToken) || response.status === 429) {\n\t\tthrow new Error(\n\t\t\t\"GitHub API rate limit exceeded while fetching release metadata; retry later or set GITHUB_TOKEN or GH_TOKEN\",\n\t\t);\n\t}\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch GitHub release metadata: ${response.statusText}`);\n\t}\n\n\treturn resolveReleaseBinaryAsset(await response.json(), tag, binaryName, { allowPrerelease });\n}\n\nexport interface VerifiedBinaryDownloadOptions {\n\turl: string;\n\ttargetPath: string;\n\texpectedSize: number;\n\texpectedDigest: string;\n\tfetchImpl?: Fetch;\n}\n","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/update-cli.ts#L254-L290","documentation":"GitHub rate-limits unauthenticated API requests (~60/hour per IP) and authenticated ones more generously. When the metadata request returns 429, or 403 while no GITHUB_TOKEN/GH_TOKEN is set (GitHub signals rate limiting via 403 for anonymous clients), the updater throws this message instead of the generic non-ok error so the remedy is obvious.","triggerScenarios":"getReleaseBinaryAsset receives response.status === 429, or response.status === 403 with githubToken undefined — checked immediately after the fetch, before the generic !response.ok branch.","commonSituations":"Shared CI egress IPs exhausting the anonymous quota, running updates repeatedly on the same IP without a token, proxies whose exit IP is already rate-limited, or automated scripts polling the releases endpoint.","solutions":["Set a GitHub token: export GITHUB_TOKEN=<personal access token> (GH_TOKEN also accepted), then retry.","Wait for the rate-limit window to reset (check X-RateLimit-Reset header); anonymous limits reset hourly.","Retry later if on shared CI infrastructure — or configure a token as a CI secret.","Reduce polling frequency if a script is hitting the endpoint repeatedly."],"exampleFix":"// before\nomp update\n// after\nexport GITHUB_TOKEN=ghp_xxxxxxxxxxxx\nomp update","handlingStrategy":"retry","validationCode":"const check = await fetch(`https://api.github.com/rate_limit`);\nconst { rate } = await check.json();\nif (rate.remaining === 0) {\n  console.error(`GitHub rate limit exhausted; resets at ${new Date(rate.reset * 1000).toISOString()}.`);\n  process.exit(1);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await update();\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"rate limit exceeded\")) {\n    console.error(\"Set GITHUB_TOKEN to raise the GitHub API limit, or wait for the hourly reset.\");\n  } else throw err;\n}","preventionTips":["Always set GITHUB_TOKEN/GH_TOKEN in CI and on shared IPs.","Check /rate_limit remaining before scripted update loops.","Back off with jitter on 403/429 instead of immediate retries.","Keep tokens scoped (public repo read is enough for release metadata)."],"tags":["network","rate-limit","github-api","update"],"backgroundTag":"api-rate-limit-exceeded","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}