{"record":{"id":"ee49ecfd76ea1b08","repo":"santifer/career-ops","slug":"local-parser-returned-invalid-json","errorCode":null,"errorMessage":"local parser returned invalid JSON","messagePattern":"local parser returned invalid JSON","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/local-parser.mjs","lineNumber":177,"sourceCode":"  const parser = entry.parser || {};\n  const { command, args } = resolveInvocation(entry);\n  const timeout = Number(parser.timeout_ms || LOCAL_PARSER_TIMEOUT_MS);\n  const maxBuffer = Number(parser.max_buffer_bytes || LOCAL_PARSER_MAX_BUFFER_BYTES);\n\n  // cwd is pinned to the project root so a relative script arg resolves to the\n  // same file resolveInvocation() validated, regardless of the caller's cwd.\n  const { stdout } = await execFileAsync(command, args, {\n    cwd: PROJECT_ROOT,\n    timeout,\n    maxBuffer,\n    windowsHide: true,\n  });\n\n  let payload;\n  try {\n    payload = JSON.parse(stdout);\n  } catch {\n    throw new Error('local parser returned invalid JSON');\n  }\n\n  const rawJobs = Array.isArray(payload) ? payload : payload.jobs || payload.results;\n  if (!Array.isArray(rawJobs)) {\n    throw new Error('local parser JSON must be an array or contain jobs[]/results[]');\n  }\n\n  return rawJobs\n    .map(job => normalizeParserJob(job, entry))\n    .filter(Boolean);\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'local-parser',\n\n  detect(entry) {\n    if (!entry.parser?.command) return null;","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/local-parser.mjs#L159-L195","documentation":"After execFileAsync runs the parser to completion, its stdout is passed to JSON.parse. If parsing fails (stdout is not valid JSON), this error fires. It is the parser-output contract: the parser must emit a single JSON document on stdout.","triggerScenarios":"The parser script printed non-JSON output: an error/stack trace to stdout, debug logging, an empty stdout, partial JSON, JSON followed by trailing text, or a different format (XML/HTML/CSV). stderr is ignored — only stdout is parsed.","commonSituations":"Parser has a stray console.log/print before the JSON; an exception dumped a traceback to stdout; the parser writes the array but appends a newline log line; the parser exited non-zero with an error message on stdout; buffer truncation (maxBuffer) cut the JSON mid-stream.","solutions":["Run the parser command manually with the same argv and inspect stdout — anything that isn't the JSON document must go to stderr.","Move debug/logging in the parser to stderr (e.g. console.error / print(..., file=sys.stderr)).","Increase parser.maxBuffer if the output was truncated (a parse error near the end often indicates truncation).","Confirm the parser exits 0; a non-zero exit with stdout text will still hit JSON.parse and fail here."],"exampleFix":"# before (parsers/acme.py)\nimport sys, json\nprint('starting parse')  # pollutes stdout\nprint(json.dumps(jobs))\n\n# after\nimport sys, json\nprint('starting parse', file=sys.stderr)  # logs go to stderr\nprint(json.dumps(jobs))    # stdout is pure JSON","handlingStrategy":"try-catch","validationCode":"// Before trusting parser output, you can pre-validate by invoking the parser\n// in a dry-run and checking stdout parses as JSON.\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nconst execFileAsync = promisify(execFile);\nexport async function parserEmitsJson(command, args) {\n  try {\n    const { stdout } = await execFileAsync(command, args, { timeout: 5000, maxBuffer: 1 << 20 });\n    JSON.parse(stdout); return true;\n  } catch { return false; }\n}","typeGuard":"/** @param {string} stdout */\nfunction isJsonDocument(stdout) {\n  try { JSON.parse(stdout); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  const jobs = await provider.fetch(entry, ctx);\n  results.push(...jobs);\n} catch (err) {\n  if (err.message === 'local parser returned invalid JSON') {\n    console.warn(`parser ${entry.name} emitted non-JSON stdout — check for stray logs/truncation`);\n  }\n  throw err;\n}","preventionTips":["Route all parser logging to stderr so stdout contains only the JSON document.","Ensure the parser exits 0 on success; non-zero exits with stdout text will fail JSON.parse here.","Size parser.maxBuffer to the expected output; truncated JSON is a common cause.","Test the parser standalone (run the exact argv) and pipe stdout through a JSON validator before wiring it in."],"tags":["json","local-parser","stdout-contract","parser-output"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}