{"record":{"id":"6c54d6a9862e9260","repo":"mihomo-party-org/clash-party","slug":"github-api-error-error-message","errorCode":null,"errorMessage":"GitHub API error: ${error.message}","messagePattern":"GitHub API error: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/utils/github.ts","lineNumber":115,"sourceCode":"          'X-GitHub-Api-Version': GITHUB_API_CONFIG.API_VERSION\n        },\n        responseType: 'json',\n        timeout: 10000\n      }\n    )\n\n    // 更新缓存\n    versionCache.set(cacheKey, {\n      data: response.data,\n      timestamp: Date.now()\n    })\n\n    log.debug(`Successfully fetched ${response.data.length} tags for ${owner}/${repo}`)\n    return response.data\n  } catch (error) {\n    log.error(`Failed to fetch tags for ${owner}/${repo}`, error)\n    if (error instanceof Error) {\n      throw new Error(`GitHub API error: ${error.message}`)\n    }\n    throw new Error('Failed to fetch version list')\n  }\n}\n\n/**\n * 清除版本缓存\n * @param owner 仓库所有者\n * @param repo 仓库名称\n */\nexport function clearVersionCache(owner: string, repo: string): void {\n  const cacheKey = `${owner}/${repo}`\n  const hasCache = versionCache.has(cacheKey)\n  versionCache.delete(cacheKey)\n  log.debug(`Cache ${hasCache ? 'cleared' : 'not found'} for ${owner}/${repo}`)\n}\n\n/**","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/utils/github.ts#L97-L133","documentation":"getGitHubTags calls the GitHub API (via Octokit) to list release tags for owner/repo. Any thrown Error from the request — rate limiting (403 with rate limit headers), 404 repo not found, network failure, auth failure — is caught, logged, and rethrown as 'GitHub API error: <original message>'. Non-Error throwables become the sibling 'Failed to fetch version list' error.","triggerScenarios":"fetchMihomoTags -> getGitHubTags with: unauthenticated requests exceeding 60/hour rate limit, network offline/proxy failure, repo renamed or private without a GITHUB_TOKEN, or GitHub 5xx outage.","commonSituations":"CI or long-running Electron app polling updates without a token and hitting the anonymous rate limit; corporate proxy blocking api.github.com; user in a region where GitHub is intermittently unreachable; checking versions during a GitHub incident.","solutions":["Read the wrapped message: 'rate limit exceeded' means supply an authenticated token; 'Not Found' means wrong owner/repo or missing auth for a private repo.","Set/provide a GitHub personal access token (even read-only) to raise the rate limit from 60 to 5000 requests/hour.","Wait for the rate-limit window to reset (check x-ratelimit-reset) or back off and retry later.","Check network/proxy connectivity to api.github.com (curl https://api.github.com/repos/MetaCubeX/mihomo/tags).","Use a cached/last-known version list while the API is unreachable."],"exampleFix":"// before\nconst tags = await getGitHubTags('MetaCubeX', 'mihomo')\n// after: token + retry with backoff\nconst octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })\nlet tags\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    tags = await octokit.repos.listTags({ owner: 'MetaCubeX', repo: 'mihomo' }).data\n    break\n  } catch (e) {\n    if (attempt === 2) throw e\n    await new Promise((r) => setTimeout(r, 2 ** attempt * 1000))\n  }\n}","handlingStrategy":"retry","validationCode":"// preflight: cheap connectivity + rate-limit check before calling the API\nconst res = await fetch('https://api.github.com/rate_limit')\nconst { remaining } = (await res.json()).resources.core\nif (remaining === 0) throw new Error('GitHub rate limit exhausted; configure a token or wait for reset')","typeGuard":null,"tryCatchPattern":"try {\n  const tags = await getGitHubTags(owner, repo)\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e)\n  if (msg.includes('rate limit')) {\n    await waitUntilRateLimitReset()\n    return getGitHubTags(owner, repo)\n  }\n  if (msg.includes('Not Found')) {\n    // repo renamed/private: check auth or repo name\n  }\n  return cachedTags // stale-but-usable fallback\n}","preventionTips":["Use an authenticated GitHub token for all API calls","Cache tag lists with a TTL instead of fetching on every check","Add exponential backoff on 403/5xx responses","Monitor x-ratelimit-remaining headers if using raw HTTP"],"tags":["network","github-api","rate-limit","http"],"backgroundTag":"github-api-rate-limit","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}