{"record":{"id":"67eff14619d53185","repo":"santifer/career-ops","slug":"themuse-unexpected-api-response-on-page-0-expec","errorCode":null,"errorMessage":"themuse: unexpected API response on page 0 — expected { results: [...] }, got keys: [${first ? Object.keys(first).join(', ') : 'null'}]","messagePattern":"themuse: unexpected API response on page 0 — expected (.+?), got keys: \\[(.+?)\\]","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/themuse.mjs","lineNumber":147,"sourceCode":"export default {\n  id: 'themuse',\n\n  async fetch(_entry, ctx) {\n    assertMuseUrl(FEED_BASE);\n\n    // Page 0 is fetched outside the tolerant loop below and its failure is\n    // NOT caught: a completely dead board must throw, not return []. A\n    // caught page-0 failure would return an empty array indistinguishable\n    // from a healthy \"0 jobs today\" result -- scan.mjs's consecutive-failure\n    // detector resets its streak on any non-throwing fetch, so a themuse\n    // outage would silently reset the very detector meant to catch it.\n    // Mirrors workday.mjs, which fetches its first page outside the\n    // retry-tolerant loop (`page = 1` start) for the same reason.\n    const firstUrl = `${FEED_BASE}?page=0`;\n    // redirect:'error' prevents SSRF via server-side redirects\n    const first = await fetchPageWithRetry(ctx, firstUrl, { redirect: 'error' });\n    if (!first || !Array.isArray(first.results)) {\n      throw new Error(\n        `themuse: unexpected API response on page 0 — expected { results: [...] }, got keys: [${first ? Object.keys(first).join(', ') : 'null'}]`,\n      );\n    }\n    const allResults = [...first.results];\n    const pageCount = Number.isInteger(first.page_count) && first.page_count > 1\n      ? Math.min(first.page_count, MAX_PAGES)\n      : 1;\n\n    // Pages 1+ stay tolerant: a page that exhausts retries, OR comes back\n    // with an unexpected shape (a successful fetch, no retry involved --\n    // caught here so it lands in the same truncation path instead of\n    // escaping uncaught and discarding allResults), truncates with a warning\n    // and returns whatever was already gathered instead of discarding it.\n    for (let page = 1; page < pageCount; page++) {\n      await sleep(INTER_PAGE_DELAY_MS, ctx);\n      const url = `${FEED_BASE}?page=${page}`;\n      let json;\n      try {","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/providers/themuse.mjs#L129-L165","documentation":"The Muse provider fetches page 0 of the feed and requires the JSON body to contain a 'results' array. If the first page is missing, not an object, or lacks a results array, fetch() throws this error naming the keys actually received. It is a fail-fast shape check before any pagination starts.","triggerScenarios":"GET {FEED_BASE}?page=0 returns JSON that is null, an array, or an object without a 'results' array — e.g. an API error payload ({error: ...}), an HTML error page parsed as text, a rate-limit response, or a schema change on the Muse API.","commonSituations":"Muse API outage or maintenance returning error JSON; API key/quota exhaustion producing an error-shaped body; The Muse changing its response envelope in a new API version; a proxy/captive portal returning its own JSON error page.","solutions":["Log/inspect the received keys (the message prints them) and compare against the documented Muse response shape.","Retry later or with backoff — transient 5xx/rate-limit responses often surface as malformed bodies.","Verify any API credentials/base URL config for the Muse provider are current.","If the Muse API changed its envelope, update the shape check in providers/themuse.mjs fetch() to the new schema.","Check network egress — a proxy or WAF may be replacing the body with its own error JSON."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isMusePage(body) {\n  return !!body && typeof body === 'object' && !Array.isArray(body) &&\n    Array.isArray(body.results);\n}","tryCatchPattern":"try {\n  const jobs = await museProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('unexpected API response on page 0')) {\n    // inspect received shape, back off, retry later\n    console.warn('Muse feed unavailable or envelope changed:', err.message);\n  } else throw err;\n}","preventionTips":["Monitor the Muse API for envelope changes; pin alerts on provider releases.","Check provider/API status before long scan runs.","Keep API credentials/quota valid — error-shaped bodies usually mean auth or rate-limit trouble.","Fail fast in CI: run a single-page smoke fetch before batch scans."],"tags":["api","schema-mismatch","http","network"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}