santifer/career-ops · error · Error

HTTP ${res.status}${snippet ? ': ' + snippet.replace(/\s+/g,

Error message

HTTP ${res.status}${snippet ? ': ' + snippet.replace(/\s+/g, ' ').trim() : ''}

What it means

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.

Source

Thrown at seeds/vc-portfolios.mjs:81

/**
 * Minimal fetch wrapper with timeout + user-agent header.
 *
 * @param {string} url
 * @param {{ timeoutMs?: number }} [opts]
 * @returns {Promise<Response>}
 */
async function fetchWithTimeout(url, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const res = await fetch(url, {
      headers: { 'user-agent': DEFAULT_USER_AGENT },
      signal: controller.signal,
    });
    if (!res.ok) {
      const snippet = await res.text().catch(() => '').then(t => t.slice(0, 200));
      throw new Error(`HTTP ${res.status}${snippet ? ': ' + snippet.replace(/\s+/g, ' ').trim() : ''}`);
    }
    return res;
  } finally {
    clearTimeout(timer);
  }
}

// ── Shared types (JSDoc only — no runtime cost) ──────────────────────

/**
 * A single VC-portfolio company entry — the output unit of both seed fetchers.
 *
 * @typedef {object} SeedCompany
 * @property {string}   name            Display name, e.g. "Stripe".
 * @property {string}   slug            URL-safe slug, validated against SLUG_RE.
 * @property {string}   url             Company website URL.
 * @property {string}   [ats]           ATS platform if detectable: 'greenhouse' | 'lever' | 'ashby'.
 * @property {string}   [ats_id]        ATS board/org slug for URL construction.

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Retry with backoff for transient 5xx/429 — the snippet tells you which.
  2. If 403/blocked, run from a residential IP or set a browser-like User-Agent (DEFAULT_USER_AGENT is already set).
  3. Check the upstream API status page / changelog for URL or auth changes.
  4. Reduce polling frequency to avoid 429 rate limiting.

Example fix

// before: single attempt
const res = await fetchWithTimeout(url);

// after: retry with backoff on transient errors
for (let attempt = 0; attempt < 3; attempt++) {
  try { return await fetchWithTimeout(url, { timeoutMs }); }
  catch (err) {
    if (attempt === 2 || !/HTTP [45]/.test(err.message)) throw err;
  }
}
Defensive patterns

Strategy: retry

Validate before calling

function classifyHttpError(err) {
  const m = err.message.match(/HTTP (\d{3})/);
  return m ? Number(m[1]) : null;
}
const status = classifyHttpError(err);
const transient = status && (status >= 500 || status === 429);

Try / catch

async function fetchWithRetry(url, opts, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try { return await fetchWithTimeout(url, opts); }
    catch (err) {
      const m = err.message.match(/HTTP (\d{3})/);
      const status = m ? Number(m[1]) : 0;
      const transient = status >= 500 || status === 429;
      if (!transient || i === retries - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
    }
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/b680b877415aeab1. Report an issue: GitHub.