{"record":{"id":"b680b877415aeab1","repo":"santifer/career-ops","slug":"http-res-status-snippet-snippet-repla","errorCode":null,"errorMessage":"HTTP ${res.status}${snippet ? ': ' + snippet.replace(/\\s+/g, ' ').trim() : ''}","messagePattern":"HTTP \\$\\{res\\.status\\}\\$\\{snippet \\? ': ' \\+ snippet\\.replace\\(/\\\\s\\+/g, ' '\\)\\.trim\\(\\) : ''\\}","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"seeds/vc-portfolios.mjs","lineNumber":81,"sourceCode":"\n/**\n * Minimal fetch wrapper with timeout + user-agent header.\n *\n * @param {string} url\n * @param {{ timeoutMs?: number }} [opts]\n * @returns {Promise<Response>}\n */\nasync function fetchWithTimeout(url, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), timeoutMs);\n  try {\n    const res = await fetch(url, {\n      headers: { 'user-agent': DEFAULT_USER_AGENT },\n      signal: controller.signal,\n    });\n    if (!res.ok) {\n      const snippet = await res.text().catch(() => '').then(t => t.slice(0, 200));\n      throw new Error(`HTTP ${res.status}${snippet ? ': ' + snippet.replace(/\\s+/g, ' ').trim() : ''}`);\n    }\n    return res;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\n// ── Shared types (JSDoc only — no runtime cost) ──────────────────────\n\n/**\n * A single VC-portfolio company entry — the output unit of both seed fetchers.\n *\n * @typedef {object} SeedCompany\n * @property {string}   name            Display name, e.g. \"Stripe\".\n * @property {string}   slug            URL-safe slug, validated against SLUG_RE.\n * @property {string}   url             Company website URL.\n * @property {string}   [ats]           ATS platform if detectable: 'greenhouse' | 'lever' | 'ashby'.\n * @property {string}   [ats_id]        ATS board/org slug for URL construction.","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/seeds/vc-portfolios.mjs#L63-L99","documentation":"fetchWithTimeout in seeds/vc-portfolios.mjs wraps node fetch and throws whenever the HTTP response status is not ok (res.ok false, i.e. status outside 200-299). To aid debugging it appends up to 200 characters of the response body (whitespace-collapsed) as a snippet. This is the library's generic non-2xx response error for all remote seed-source fetches.","triggerScenarios":"Any fetch call made through fetchWithTimeout that returns 404 (page moved), 403 (bot blocked / rate limited), 429 (too many requests), 5xx (server error), or a redirect-to-error from the remote host. The error is thrown inside the try block and then re-wrapped by callers like fetchYCCompanies/fetchA16zCompanies.","commonSituations":"Hitting api.ycombinator.com while rate-limited or during an outage; a company's portfolio page returning 403 because of Cloudflare bot protection; a typo'd or deprecated endpoint URL; corporate proxy returning an error page.","solutions":["Read the status code and body snippet in the message to identify the cause (403/429 → rate limiting or bot protection; 404 → wrong URL; 5xx → retry later)","Retry with exponential backoff after a delay, especially for 429/5xx responses","Send a realistic User-Agent (the library already sends DEFAULT_USER_AGENT; some hosts require a browser-like UA or an API key)","Verify the endpoint URL is still valid (APIs change; check the provider's docs) and update A16Z_PORTFOLIO_URL / the YC API URL if deprecated"],"exampleFix":"// before\nconst res = await fetchWithTimeout(url, { timeoutMs });\n// after: retry with backoff on transient statuses\nlet res;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { res = await fetchWithTimeout(url, { timeoutMs }); break; }\n  catch (e) {\n    if (!/HTTP (429|5\\d\\d)/.test(e.message) || attempt === 2) throw e;\n    await new Promise(r => setTimeout(r, 2 ** attempt * 1000));\n  }\n}","handlingStrategy":"retry","validationCode":"// No pre-call validation possible for remote status; check reachability/health first:\nconst healthy = await fetch(url, { method: 'HEAD' }).then(r => r.ok).catch(() => false);\nif (!healthy) console.warn('endpoint unhealthy — expect HTTP non-2xx');","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetchWithTimeout(url, { timeoutMs });\n  // use res\n} catch (err) {\n  const m = /HTTP (\\d{3})/.exec(err.message);\n  const status = m ? Number(m[1]) : 0;\n  if (status === 429 || status >= 500) { /* back off and retry */ }\n  else if (status === 403 || status === 404) { /* fix UA/URL — retrying won't help */ }\n  else throw err;\n}","preventionTips":["Respect rate limits; add delays/backoff between seed fetches","Send an appropriate User-Agent for each host","Pin/verify endpoint URLs against provider docs on each system update","Monitor for Cloudflare/bot-protection pages and switch to official APIs where possible"],"tags":["http","network","fetch","api"],"backgroundTag":"http-non-2xx-response","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":"2026-09-01T19:19:23.111Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}