santifer/career-ops · error · Error

local parser JSON must be an array or contain…

Error message

local parser JSON must be an array or contain jobs[]/results[]

What it means

After parsing stdout as JSON, runLocalParser() requires the payload to be a top-level array or an object with a jobs[] or results[] array. Any other shape (a bare object of metadata, a {data: ...} wrapper, a single job object) is rejected so the provider never silently yields zero jobs from a misinterpreted payload.

Solutions

  1. Change the parser to emit a top-level array, or wrap the list under jobs or results: console.log(JSON.stringify({jobs: raw}))
  2. If the upstream API uses a `data` envelope, unwrap it in the parser before printing.
  3. When only one job is found, still emit an array ([job]) not the bare object.
  4. As a consumer, catch this error and inspect the parser's actual output shape before changing config — it signals a contract mismatch between parser script and provider, not a network failure.

Example fix

// before (parser script)
console.log(JSON.stringify({ data: jobs }));
// after
console.log(JSON.stringify({ jobs: jobs }));
Defensive patterns

Strategy: type-guard

Validate before calling

const payload = JSON.parse(stdout);
const ok = Array.isArray(payload) || Array.isArray(payload?.jobs) || Array.isArray(payload?.results);
if (!ok) throw new Error('parser output must be [] or {jobs:[…]}/{results:[…]}');

Type guard

function isParserPayload(p) {
  return Array.isArray(p) || Array.isArray(p?.jobs) || Array.isArray(p?.results);
}

Try / catch

try {
  const jobs = await localParser.fetch(entry);
} catch (e) {
  if (String(e.message).includes('must be an array or contain jobs[]/results[]')) {
    console.error(`${entry.name}: unwrap the upstream envelope ({data:[…]}) into {jobs:[…]} in the parser`);
    return [];
  }
  throw e;
}

Prevention

When it happens

Trigger: Parser prints valid JSON whose shape is unsupported — e.g. {"data": [...]}, {"postings": [...]}, or a single {"title":..., "url":...} object — so payload.jobs and payload.results are both undefined and the Array.isArray check fails.

Common situations: Parser written for a different consumer expecting {data: [...]}; API response passed through verbatim including an envelope (count/next/data); emitting one JSON object instead of an array when only one job exists; key renamed from jobs to openings in a parser refactor.

Related errors


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

Appendix: source

Thrown at providers/local-parser.mjs:182

  // cwd is pinned to the project root so a relative script arg resolves to the
  // same file resolveInvocation() validated, regardless of the caller's cwd.
  const { stdout } = await execFileAsync(command, args, {
    cwd: PROJECT_ROOT,
    timeout,
    maxBuffer,
    windowsHide: true,
  });

  let payload;
  try {
    payload = JSON.parse(stdout);
  } catch {
    throw new Error('local parser returned invalid JSON');
  }

  const rawJobs = Array.isArray(payload) ? payload : payload.jobs || payload.results;
  if (!Array.isArray(rawJobs)) {
    throw new Error('local parser JSON must be an array or contain jobs[]/results[]');
  }

  return rawJobs
    .map(job => normalizeParserJob(job, entry))
    .filter(Boolean);
}

/** @type {Provider} */
export default {
  id: 'local-parser',

  detect(entry) {
    if (!entry.parser?.command) return null;

    // An invocation we can't safely resolve (unknown command, out-of-repo or
    // missing script, inline-code flags) is not runnable — skip it.
    try {
      resolveInvocation(entry);

View on GitHub (pinned to aac998c7ed)