{"record":{"id":"537e4a941d431fff","repo":"nextlevelbuilder/ui-ux-pro-max-skill","slug":"github-api-rate-limit-exceeded-resets-at-resetd","errorCode":null,"errorMessage":"GitHub API rate limit exceeded. Resets at ${resetDate}.\\n${getGitHubTokenGuidance()}","messagePattern":"GitHub API rate limit exceeded\\. Resets at (.+?)\\.\\\\n(.+?)","errorType":"http","errorClass":"GitHubRateLimitError","httpStatus":403,"severity":"error","filePath":"cli/src/utils/github.ts","lineNumber":38,"sourceCode":"  }\n}\n\nexport function getGitHubTokenGuidance(): string {\n  return (\n    'To increase your GitHub API rate limit, set the UI_PRO_MAX_GITHUB_TOKEN environment variable\\n' +\n    'to a GitHub Personal Access Token (no scopes needed for public repos).\\n' +\n    'Create one at: https://github.com/settings/tokens\\n' +\n    'Example: UI_PRO_MAX_GITHUB_TOKEN=ghp_xxx uipro init\\n' +\n    'Or pass it directly: uipro init --token ghp_xxx'\n  );\n}\n\nfunction checkRateLimit(response: Response): void {\n  const remaining = response.headers.get('x-ratelimit-remaining');\n  if (response.status === 403 && remaining === '0') {\n    const resetTime = response.headers.get('x-ratelimit-reset');\n    const resetDate = resetTime ? new Date(parseInt(resetTime) * 1000).toLocaleTimeString() : 'unknown';\n    throw new GitHubRateLimitError(\n      `GitHub API rate limit exceeded. Resets at ${resetDate}.\\n${getGitHubTokenGuidance()}`\n    );\n  }\n  if (response.status === 429) {\n    throw new GitHubRateLimitError(\n      `GitHub API rate limit exceeded (429 Too Many Requests).\\n${getGitHubTokenGuidance()}`\n    );\n  }\n}\n\nfunction getAuthHeaders(token?: string): Record<string, string> {\n  const resolved = (token || process.env['UI_PRO_MAX_GITHUB_TOKEN'] || process.env['GITHUB_TOKEN'])?.trim();\n  return resolved ? { 'Authorization': `Bearer ${resolved}` } : {};\n}\n\nexport async function fetchReleases(token?: string): Promise<Release[]> {\n  const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases`;\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5/cli/src/utils/github.ts#L20-L56","documentation":"Rate-limit guard in checkRateLimit(): when the GitHub API responds 403 and the x-ratelimit-remaining header is exactly '0', the CLI throws GitHubRateLimitError with the reset time (from x-ratelimit-reset) plus guidance to use a token. Unauthenticated GitHub API requests are limited to 60/hour per IP, so this is the classic anonymous-quota exhaustion on `uipro init`.","triggerScenarios":"Calling getLatestRelease()/fetchReleases() (i.e. running `uipro init`) without UI_PRO_MAX_GITHUB_TOKEN or GITHUB_TOKEN set, from an IP that has already made ~60 API calls in the past hour — typical in shared CI runners, offices, or VPNs behind one NAT IP.","commonSituations":"CI pipelines on shared runner pools exhausting the anonymous quota; multiple developers behind the same corporate egress IP; a loop or retry storm in a script calling the installer repeatedly.","solutions":["Set a token: `UI_PRO_MAX_GITHUB_TOKEN=ghp_xxx uipro init` or `uipro init --token ghp_xxx` (no scopes needed for public repos) — raises the limit to 5,000/hour.","Create the token at https://github.com/settings/tokens if you don't have one.","If you just hit the anonymous limit, wait until the reset time shown in the message and retry.","Cache the downloaded release in CI (or run install once and reuse the target dir) so each job doesn't consume API quota."],"exampleFix":"# before\nuipro init\n# after\nexport UI_PRO_MAX_GITHUB_TOKEN=ghp_xxx\nuipro init","handlingStrategy":"fallback","validationCode":"function hasGitHubToken(): boolean {\n  return Boolean(\n    (process.env.UI_PRO_MAX_GITHUB_TOKEN || process.env.GITHUB_TOKEN || '').trim()\n  );\n}\n// warn BEFORE calling the API when anonymous\nif (!hasGitHubToken()) {\n  console.warn('No GitHub token set: anonymous rate limit is 60 req/hour per IP.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await installFromGitHub(targetDir, aiType, spinner, token);\n} catch (e) {\n  if (e instanceof GitHubRateLimitError) {\n    spinner.fail(e.message); // message already includes reset time + token guidance\n    process.exitCode = 1; // or fall back to local assets\n  } else throw e;\n}","preventionTips":["Always export UI_PRO_MAX_GITHUB_TOKEN in CI before running uipro init.","Cache the downloaded release (keyed by tag) so repeat installs make zero API calls.","Never retry immediately on 403/remaining=0 — wait for the reset time shown in the message."],"tags":["github-api","rate-limit","network","cli","auth"],"backgroundTag":null,"analyzedSha":"a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5","analyzedAt":"2026-08-14T18:51:02.321Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}