{"record":{"id":"7ffced65af7495d3","repo":"can1357/oh-my-pi","slug":"github-api-error-response-status","errorCode":null,"errorMessage":"GitHub API error: ${response.status}","messagePattern":"GitHub API error: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/utils/tools-manager.ts","lineNumber":188,"sourceCode":"}\n\n// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string, signal?: AbortSignal): Promise<string> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\t\theaders: { \"User-Agent\": USER_AGENT },\n\t\t\tsignal: ptree.combineSignals(signal, TOOL_METADATA_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tif (err instanceof Error && err.name === \"AbortError\") {\n\t\t\tthrow new Error(\"GitHub API request timed out\");\n\t\t}\n\t\tthrow err;\n\t}\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n/** Download a tool asset without handing the streaming Response to Bun.write. */\nexport async function downloadFile(url: string, dest: string, signal?: AbortSignal): Promise<void> {\n\tconst downloadSignal = ptree.combineSignals(signal, TOOL_DOWNLOAD_TIMEOUT_MS);\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tsignal: downloadSignal,\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t\t} else if (!response.body) {\n\t\t\tthrow new Error(\"No response body\");","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/utils/tools-manager.ts#L170-L206","documentation":"After fetching the latest-release endpoint, getLatestVersion() checks response.ok. Any non-2xx status (404 repo missing, 403 rate limited, 5xx server errors) is thrown as this error carrying the numeric HTTP status, giving callers a direct signal of what GitHub rejected.","triggerScenarios":"The GET to https://api.github.com/repos/<repo>/releases/latest completes but returns a non-OK status — e.g. 403 with rate-limit exhausted (unauthenticated GitHub API allows ~60 req/hr per IP), 404 when the configured repo or its releases no longer exist, or 5xx during GitHub incidents.","commonSituations":"Shared CI runners exhausting the unauthenticated GitHub rate limit, a tool upstream renaming/removing its repo or stopping publishing GitHub releases, firewalled networks returning error pages, transient GitHub 5xx outages.","solutions":["Read the status in the message: 403/429 → rate limited; 404 → repo/releases gone; 5xx → retry later.","For rate limiting, wait for the rate-limit window to reset (check X-RateLimit-Reset) or use authenticated requests.","Verify the tool's repo still publishes GitHub releases; if releases moved, update the TOOLS[tool].repo config.","Retry with backoff for transient 5xx responses.","If the status persists, install the tool manually and skip the auto-update path."],"exampleFix":"// before: const version = await toolsManager.version(\"rg\") // throws \"GitHub API error: 403\" | // after: catch, and if /GitHub API error: (403|429)/ matches, fall back to pinned version; rethrow otherwise","handlingStrategy":"fallback","validationCode":"// check unauthenticated rate budget before calling | const rl = await (await fetch(\"https://api.github.com/rate_limit\")).json(); if (rl.resources.core.remaining === 0) throw new Error(\"GitHub rate limit exhausted\");","typeGuard":"function isGitHubApiError(err: unknown): err is Error { return err instanceof Error && /^GitHub API error: \\d+$/.test(err.message); }","tryCatchPattern":"try { const version = await toolsManager.version(\"sg\"); } catch (err) { if (/GitHub API error: (403|429)/.test(err.message)) { /* rate limited: cached version or authenticated request */ } else if (/GitHub API error: 5\\d\\d/.test(err.message)) { /* transient: retry later */ } else throw err; }","preventionTips":["Authenticate GitHub API requests when running on shared CI to avoid the 60 req/hr IP limit.","Cache the last-known version and treat the API call as best-effort.","Keep a pinned fallback version per tool.","Watch GitHub status for 5xx windows before bulk downloads."],"tags":["network","github-api","http-error","rate-limit"],"backgroundTag":"github-api-http-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}