santifer/career-ops · error · Error
HTTP
Error message
HTTP ${res.status}${snippet ? ': ' + snippet.replace(/\s+/g, ' ').trim() : ''} What it means
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.
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
Example fix
// before
const res = await fetchWithTimeout(url, { timeoutMs });
// after: retry with backoff on transient statuses
let res;
for (let attempt = 0; attempt < 3; attempt++) {
try { res = await fetchWithTimeout(url, { timeoutMs }); break; }
catch (e) {
if (!/HTTP (429|5\d\d)/.test(e.message) || attempt === 2) throw e;
await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
}
} Defensive patterns
Strategy: retry
Validate before calling
// No pre-call validation possible for remote status; check reachability/health first:
const healthy = await fetch(url, { method: 'HEAD' }).then(r => r.ok).catch(() => false);
if (!healthy) console.warn('endpoint unhealthy — expect HTTP non-2xx'); Try / catch
try {
const res = await fetchWithTimeout(url, { timeoutMs });
// use res
} catch (err) {
const m = /HTTP (\d{3})/.exec(err.message);
const status = m ? Number(m[1]) : 0;
if (status === 429 || status >= 500) { /* back off and retry */ }
else if (status === 403 || status === 404) { /* fix UA/URL — retrying won't help */ }
else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Could not fetch job page
- TIMEOUT
- torre: unexpected API response — expected
- vc-portfolios: YC API fetch failed
- All active models failed. Last error
AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01).
Data as JSON: /api/errors/b680b877415aeab1.
Report an issue: GitHub.
Appendix: 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 1696bec4d0)