{"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":"exception","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/9b17a8ac97b398a496b38e423ae24e433b43254f/seeds/vc-portfolios.mjs#L63-L99","documentation":"fetchWithTimeout() in seeds/vc-portfolios.mjs performs an HTTP fetch with an AbortController-based timeout. If the response status is not ok (not 2xx), it reads up to 200 chars of the body as a snippet and throws an Error of the form `HTTP <status>: <snippet>`. This surfaces upstream server errors (4xx/5xx) with actionable context.","triggerScenarios":"The VC portfolio API (YC or a16z endpoint) returns a non-2xx status: 403 (blocked/rate-limited), 404 (endpoint moved), 429 (rate limit), 500/502/503 (server error), or a CDN block page.","commonSituations":"Running scans from a datacenter IP that triggers Cloudflare blocks; hitting rate limits from aggressive polling; the upstream API changed its URL or requires auth; transient 5xx during maintenance.","solutions":["Retry with backoff for transient 5xx/429 — the snippet tells you which.","If 403/blocked, run from a residential IP or set a browser-like User-Agent (DEFAULT_USER_AGENT is already set).","Check the upstream API status page / changelog for URL or auth changes.","Reduce polling frequency to avoid 429 rate limiting."],"exampleFix":"// before: single attempt\nconst res = await fetchWithTimeout(url);\n\n// after: retry with backoff on transient errors\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { return await fetchWithTimeout(url, { timeoutMs }); }\n  catch (err) {\n    if (attempt === 2 || !/HTTP [45]/.test(err.message)) throw err;\n  }\n}","handlingStrategy":"retry","validationCode":"function classifyHttpError(err) {\n  const m = err.message.match(/HTTP (\\d{3})/);\n  return m ? Number(m[1]) : null;\n}\nconst status = classifyHttpError(err);\nconst transient = status && (status >= 500 || status === 429);","typeGuard":null,"tryCatchPattern":"async function fetchWithRetry(url, opts, retries = 3) {\n  for (let i = 0; i < retries; i++) {\n    try { return await fetchWithTimeout(url, opts); }\n    catch (err) {\n      const m = err.message.match(/HTTP (\\d{3})/);\n      const status = m ? Number(m[1]) : 0;\n      const transient = status >= 500 || status === 429;\n      if (!transient || i === retries - 1) throw err;\n      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));\n    }\n  }\n}","preventionTips":["Wrap fetchWithTimeout in a retry-with-backoff for transient (5xx, 429) errors.","Respect Retry-After headers on 429/503 responses.","Run VC-portfolio fetches from IPs that are not datacenter-blocked when possible."],"tags":["network","http","seeds","vc-portfolios","rate-limiting"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}