{"record":{"id":"a5e444d3235b3715","repo":"santifer/career-ops","slug":"provider-id-fetch-did-not-return-an-array","errorCode":null,"errorMessage":"${provider.id}: fetch() did not return an array","messagePattern":"(.+?): fetch\\(\\) did not return an array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scan.mjs","lineNumber":3136,"sourceCode":"    let sourceName = provider.id === 'local-parser' ? 'local-parser' : `${provider.id}-api`;\n    try {\n      let jobs;\n      try {\n        jobs = await provider.fetch(company, ctx);\n      } catch (parserErr) {\n        if (provider.id !== 'local-parser') throw parserErr;\n        const fallback = resolveProvider(company, providers, { skipIds: ['local-parser'] });\n        if (!fallback || fallback.error) throw parserErr;\n        provider = fallback.provider;\n        sourceName = `${provider.id}-api`;\n        jobs = await provider.fetch(company, ctx);\n        errors.push({\n          company: company.name,\n          error: `local parser failed, used API fallback: ${parserErr.message}`,\n        });\n      }\n      if (!Array.isArray(jobs)) {\n        throw new Error(`${provider.id}: fetch() did not return an array`);\n      }\n      totalFound += jobs.length;\n      if (!company._isBoard && jobs.length === 0) {\n        emptyTargets.push(company.name);\n      }\n\n      for (const job of jobs) {\n        // Trust enrichment — runs before filters, never drops\n        const trustResult = trustValidator(job);\n        job.trustScore = trustResult.score;\n        job.trustFlags = trustResult.flags;\n        job.trustLevel = trustResult.level;\n\n        // Company blacklist (#1742) — the user's own do-not-apply decision,\n        // checked first: it's company-level, not a per-posting signal. Never\n        // silent: skips are counted and reported in the run summary, and\n        // --include-blacklisted lets the posting through annotated instead.\n        if (blacklist.size > 0) {","sourceCodeStart":3118,"sourceCodeEnd":3154,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/scan.mjs#L3118-L3154","documentation":"scan.mjs validates that each ATS provider's fetch() returns an array of job postings before accumulating results; this error is thrown when a provider returns null, undefined, or any non-array value. It is an internal contract check that turns a malformed provider response into a per-company error instead of corrupting the totals.","triggerScenarios":"A provider module's fetch() returns undefined on an unhandled code path, returns an object instead of an array (API shape change), or a local parser failure paths leaves `jobs` unset before the API fallback assigns it.","commonSituations":"Upstream ATS API changes its response envelope so the provider's normalization returns a non-array; a provider bug where an early return skips array construction; a provider returning `null` to signal 'no board' instead of an empty array.","solutions":["Inspect the provider's fetch() in providers/ and ensure every code path returns an array (return [] instead of null/undefined)","Check the upstream ATS API response shape for recent changes and update the provider's normalization","Log the actual returned value to identify which provider code path skipped array construction","Wrap provider parsing so fallback paths always assign an array to `jobs` before the Array.isArray check"],"exampleFix":"// before (provider fetch)\nasync fetch() {\n  if (!boardId) return;               // returns undefined\n  const jobs = await api.get(boardId);\n  return jobs.map(normalize);\n}\n// after\nasync fetch() {\n  if (!boardId) return [];\n  const jobs = await api.get(boardId);\n  return Array.isArray(jobs) ? jobs.map(normalize) : [];\n}","handlingStrategy":"type-guard","validationCode":"const jobs = await provider.fetch();\nif (!Array.isArray(jobs)) {\n  throw new Error(`${provider.id}: fetch() returned ${jobs === null ? 'null' : typeof jobs}, expected array`);\n}","typeGuard":"function isJobArray(v) {\n  return Array.isArray(v) && v.every(j => j && typeof j === 'object' && 'title' in j);\n}","tryCatchPattern":"try {\n  jobs = await provider.fetch();\n} catch (err) {\n  errors.push({ company: company.name, error: `provider fetch failed: ${err.message}` });\n  jobs = []; // never leave jobs undefined before the Array.isArray check\n}\nif (!Array.isArray(jobs)) jobs = [];","preventionTips":["Every provider fetch() path must `return []` rather than null/undefined on empty or error paths","Add a provider conformance test asserting fetch() resolves to an array","Re-verify provider normalization whenever an upstream ATS API changes its response envelope"],"tags":["provider","api","contract","array"],"backgroundTag":"unexpected-response-shape","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}