{"record":{"id":"522eea4a7a002342","repo":"justjavac/wechat-miniapp-radar","slug":"http-response-status","errorCode":null,"errorMessage":"HTTP ${response.status}","messagePattern":"HTTP \\$\\{response\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"lib/enrichment.ts","lineNumber":94,"sourceCode":"    return parts[1] ?? null;\n  } catch {\n    return null;\n  }\n}\n\nasync function fetchJson<T>(url: string, headers: HeadersInit = {}) {\n  const controller = new AbortController();\n  const timeout = setTimeout(() => controller.abort(), 10000);\n\n  try {\n    const response = await fetch(url, {\n      headers: {\n        \"user-agent\": \"miniprogram-radar\",\n        ...headers\n      },\n      signal: controller.signal\n    });\n    if (!response.ok) throw new Error(`HTTP ${response.status}`);\n    return (await response.json()) as T;\n  } finally {\n    clearTimeout(timeout);\n  }\n}\n\nasync function fetchHead(url: string) {\n  const controller = new AbortController();\n  const timeout = setTimeout(() => controller.abort(), 8000);\n\n  try {\n    const response = await fetch(url, {\n      method: \"HEAD\",\n      headers: {\n        \"user-agent\": \"miniprogram-radar\"\n      },\n      redirect: \"follow\",\n      signal: controller.signal","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/justjavac/wechat-miniapp-radar/blob/02a010ecea0320b7aa975bb62a5dde271ae630c9/lib/enrichment.ts#L76-L112","documentation":"Thrown by fetchJson() in lib/enrichment.ts when an HTTP response is non-OK; it raises a bare `HTTP <status>` carrying only the status code. fetchJson() is the internal helper used to pull GitHub repo metadata and npm registry data during signal collection, with a 10s AbortController timeout (an abort surfaces as a different AbortError, not this message). The public collector collectResourceSignal() wraps every fetchJson() call in try/catch and converts the thrown error into a CollectedSignal with ok:false and the message in .error, so it never escapes to top-level code.","triggerScenarios":"GitHub repos API returns 403 (unauthenticated rate limit of 60 req/hour, or an expired GITHUB_TOKEN); 404 for a renamed or deleted repo or npm package; 401 for a bad token; 5xx from the registry; a registry/site returning a non-2xx status for an otherwise valid URL.","commonSituations":"Running enrichment locally without GITHUB_TOKEN and hitting the 60/hr limit; a tracked repo renamed or moved; npm package unpublished; flaky 5xx during a bulk run; GITHUB_TOKEN expired but still set in env.","solutions":["Set GITHUB_TOKEN to raise GitHub limits from 60 to about 5000 req/hour (collectResourceSignal already attaches it as Bearer when present).","For 404s, correct or remove the resource URL in data/resources.yaml then run npm run generate.","For transient 403-rate-limit or 5xx, retry with backoff or reduce runEnrichment({ limit }).","Always read the returned CollectedSignal.ok/.error rather than letting fetchJson propagate."],"exampleFix":"// before\nconst repo = await fetchJson<GitHubRepo>(url); // throws 'HTTP 403'\n\n// after (the pattern collectResourceSignal already uses)\ntry {\n  const repo = await fetchJson<GitHubRepo>(url, headers);\n} catch (error) {\n  return { ok: false, error: error instanceof Error ? error.message : String(error) };\n}","handlingStrategy":"try-catch","validationCode":"const needsGithubToken = resources.some(r => r.url.startsWith('https://github.com/'));\nif (needsGithubToken && !process.env.GITHUB_TOKEN) {\n  console.warn('GITHUB_TOKEN unset; GitHub fetches may 403 after 60 req/hour');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const data = await fetchJson<T>(url, headers);\n} catch (error) {\n  const message = error instanceof Error ? error.message : String(error); // e.g. 'HTTP 403'\n  return { ok: false, payload: {}, error: message } satisfies CollectedSignal;\n}","preventionTips":["Always set GITHUB_TOKEN for enrichment jobs.","Wrap every fetchJson call like collectResourceSignal does; never let it propagate.","Cap concurrency with runEnrichment({ limit }) to stay under rate ceilings."],"tags":["network","http","rate-limit","enrichment"],"backgroundTag":null,"analyzedSha":"02a010ecea0320b7aa975bb62a5dde271ae630c9","analyzedAt":"2026-08-12T16:16:33.227Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}