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
- Change the parser to emit a top-level array, or wrap the list under jobs or results: console.log(JSON.stringify({jobs: raw}))
- If the upstream API uses a `data` envelope, unwrap it in the parser before printing.
- When only one job is found, still emit an array ([job]) not the bare object.
- 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
- Agree on the payload contract (array or jobs[]/results[]) for every parser script and document it in the script header.
- Unwrap API envelopes (data/postings) inside the parser before printing.
- Always emit an array even for a single job.
- Add a unit test per parser asserting isParserPayload(JSON.parse(output)).
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
- Existing candidates file at
- invalid golden case : need string id/jd and label.
- jobicy: unexpected API response — expected
- local parser returned invalid JSON
- remotli: unexpected API response — expected
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)