{"record":{"id":"49a2326670bc63da","repo":"mihomo-party-org/clash-party","slug":"request-failed-with-status-res-status-url","errorCode":null,"errorMessage":"Request failed with status ${res.status}: ${url}","messagePattern":"Request failed with status (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/resolve/autoUpdater.ts","lineNumber":54,"sourceCode":"\nfunction buildDownloadUrls(githubUrl: string, proxyPref = ''): string[] {\n  if (proxyPref === 'direct') return [githubUrl]\n  if (proxyPref && proxyPref !== 'auto') return [`${proxyPref}/${githubUrl}`]\n  // auto: try each proxy then fall back to direct\n  return [...GITHUB_PROXIES.map((p) => `${p}/${githubUrl}`), githubUrl]\n}\n\nasync function tryDownload(\n  urls: string[],\n  options: Parameters<typeof chromeRequest.get>[1]\n): Promise<Awaited<ReturnType<typeof chromeRequest.get>>> {\n  let lastError: unknown\n  for (const url of urls) {\n    try {\n      const res = await chromeRequest.get(url, options)\n      // 代理源限流/失效时会以 200 以外的状态返回错误页，必须当作失败才能继续尝试下一个源\n      if (res.status < 200 || res.status >= 300) {\n        throw new Error(`Request failed with status ${res.status}: ${url}`)\n      }\n      return res\n    } catch (e) {\n      lastError = e\n    }\n  }\n  throw lastError\n}\n\ntype UpdaterProxy = { protocol: 'http'; host: string; port: number } | false\n\n// 用户关闭混合端口时配置里写的是 0（不是 undefined），解构默认值挡不住。\n// 直接拿 0 去拼代理会打到 127.0.0.1:0，检查更新和下载更新都必然失败，\n// 所以端口未启用时要显式走直连。\nfunction updaterProxy(mixedPort: number): UpdaterProxy {\n  return mixedPort ? { protocol: 'http', host: '127.0.0.1', port: mixedPort } : false\n}\n","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/resolve/autoUpdater.ts#L36-L72","documentation":"tryDownload in src/main/resolve/autoUpdater.ts:54 iterates a list of candidate download URLs (direct + proxied GitHub mirrors). Any HTTP status outside 200-299 is treated as failure: it throws `Request failed with status ${res.status}: ${url}`, which is captured into lastError and the loop continues to the next mirror. If all mirrors fail, the last such error propagates — meaning the reported status/url belongs to the final attempted source.","triggerScenarios":"chromeRequest.get returns res.status 403/429 (rate limiting), 404 (asset removed or tag renamed), or 5xx from a proxy mirror; the thrown error is either intermediate (a later mirror succeeds) or final (all urls exhausted).","commonSituations":"GitHub proxy sources (ghproxy-style mirrors) are rate-limited or dead and return non-2xx error pages; a release tag was deleted or renamed so the asset 404s; corporate firewalls block the proxy domain; GitHub API rate limits during version checks.","solutions":["Retry the update later — proxy mirrors are often temporarily rate-limited (429/403)","Switch or clear the configured githubProxy setting in app config to use a different/working mirror","Verify the release tag and asset still exist on GitHub (404 means the asset was removed)","Check network/firewall access to both direct GitHub and the proxy host","If all mirrors consistently fail, inspect lastError in tryDownload logs to see which status each source returned"],"exampleFix":"// before\nconst res = await chromeRequest.get(deadMirrorUrl, options)\n// after — skip a failing proxy by using the direct URL or a healthy mirror\nconst res = await chromeRequest.get('https://github.com/owner/repo/releases/download/v1.2.3/app.exe', options)","handlingStrategy":"retry","validationCode":"// pre-check a mirror before relying on it\nconst probe = await fetch(mirrorUrl, { method: 'HEAD' })\nif (!probe.ok) console.warn(`mirror ${mirrorUrl} unhealthy: ${probe.status}`)","typeGuard":"function isOkStatus(status: number): boolean {\n  return status >= 200 && status < 300\n}","tryCatchPattern":"try {\n  const res = await tryDownload(urls, options)\n  install(res)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Request failed with status')) {\n    // all mirrors exhausted; surface status from message and retry later\n    safeShowErrorBox('updater.downloadFailed', e.message)\n  }\n}","preventionTips":["Keep multiple independent mirrors in the urls list so one 403/429 does not end the download","Rotate or health-check githubProxy mirrors periodically; remove dead ones from config","Treat any non-2xx as failure (as tryDownload does) instead of parsing error pages","Retry with backoff on 429/5xx; check release assets exist before downloading (404)"],"tags":["network","http","updater","proxy","mirror"],"backgroundTag":"http-non-2xx-response","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}