santifer/career-ops · error · Error

a16z-speedrun-talent: unexpected API response on page

Error message

a16z-speedrun-talent: unexpected API response on page ${page} — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]

What it means

Thrown by the a16z-speedrun-talent provider's fetch when a page of its paginated feed returns JSON that lacks a `jobs` array. Each page is validated (json truthy and Array.isArray(json.jobs)) after a bounded retry via fetchJsonWithRetry — the retries cover transient blips mid-sweep because a single failure used to abort the whole provider and return nothing (#2506). Once retries are exhausted or the shape is still wrong, this error propagates: a silent partial board is considered worse than a loud failure. The message lists the actual top-level keys for diagnosis.

Solutions

  1. Fetch the page URL manually and compare the reported keys against the expected { jobs: [...] } shape.
  2. Check whether the feed API migrated/changed schema and update normalizeSpeedrunJob/parsing accordingly.
  3. Cap max_pages or stop at the first page whose job list is empty to avoid querying non-existent pages.
  4. If a proxy/WAF rewrites responses, bypass it so the real feed JSON arrives.
Defensive patterns

Strategy: retry

Validate before calling

const json = await fetchJsonWithRetry(ctx, url, { redirect: 'error' });
if (!json || !Array.isArray(json.jobs)) {
  throw new Error(`speedrun feed shape changed on ${url}: keys=${Object.keys(json ?? {}).join(',')}`);
}

Type guard

function isSpeedrunJobsPage(json) {
  return typeof json === 'object' && json !== null && Array.isArray(json.jobs);
}

Try / catch

try {
  const jobs = await fetchSpeedrunFeedPage(page);
} catch (e) {
  if (String(e.message).includes('unexpected API response')) {
    console.error(`Speedrun feed shape unexpected at page ${page}; schema may have changed`);
    return partialResults; // decide loudly, never silently truncate
  } else throw e;
}

Prevention

When it happens

Trigger: fetchJsonWithRetry on a feed page URL returns null, an object without `jobs`, or `jobs` not an array — API schema change, error payload with 200 status, or last-page/overflow page returning a different body; retries (redirect:'error') already exhausted.

Common situations: The speedrun talent feed API changed its response shape; requesting pages beyond the feed's end; a WAF/CDN serving JSON error bodies; an outage returning {message:'...'} objects that survive retries.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/916d448f0d28a56b. Report an issue: GitHub.

Appendix: source

Thrown at providers/a16z-speedrun-talent.mjs:171

    assertFeedUrl(FEED_BASE);
    const maxPages = resolveMaxPages(entry);
    const q = resolveQuery(entry);
    const fallbackCompany = entry?.name;
    const out = [];

    for (let page = 0; page < maxPages; page++) {
      const params = new URLSearchParams({ page: String(page), source: 'career-ops' });
      if (q) params.set('q', q);
      const url = `${FEED_BASE}?${params}`;
      // redirect:'error' prevents SSRF via server-side redirects.
      // Retried on transient upstream failures (429/5xx/timeout): this board
      // paginates into the hundreds of pages, so a single blip mid-sweep used
      // to abort the whole provider and return NOTHING. Retries are bounded
      // and, once exhausted, the error still propagates — a silent partial
      // board would be worse than an loud empty one (#2506).
      const json = await fetchJsonWithRetry(ctx, url, { redirect: 'error' });
      if (!json || !Array.isArray(json.jobs)) {
        throw new Error(
          `a16z-speedrun-talent: unexpected API response on page ${page} — expected { jobs: [...] }, got keys: [${json ? Object.keys(json).join(', ') : 'null'}]`,
        );
      }
      for (const j of json.jobs) {
        const normalized = normalizeSpeedrunJob(j, fallbackCompany);
        if (normalized) out.push(normalized);
      }
      // Stop at the last page. Termination priority (#2547):
      //   1. An empty page always ends iteration — nothing left to read, and
      //      it bounds a feed that over-reports total_pages instead of burning
      //      requests up to max_pages.
      //   2. The feed's own total_pages when present. A page that comes back
      //      49/50 because a listing was deleted between requests on a board
      //      that rotates mid-sweep is NOT the last page; treating it as one
      //      ended the sweep silently, at a clean exit code — the cap warning
      //      below is gated on max_pages and is never reached from here.
      //      A non-positive total_pages is not a statement about board size,
      //      so it counts as absent: a feed reporting 0 alongside 50 real

View on GitHub (pinned to aac998c7ed)