santifer/career-ops · error · Error

local parser returned invalid JSON

Error message

local parser returned invalid JSON

What it means

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.

Source

Thrown at providers/local-parser.mjs:177

  const parser = entry.parser || {};
  const { command, args } = resolveInvocation(entry);
  const timeout = Number(parser.timeout_ms || LOCAL_PARSER_TIMEOUT_MS);
  const maxBuffer = Number(parser.max_buffer_bytes || LOCAL_PARSER_MAX_BUFFER_BYTES);

  // 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;

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Run the parser command manually with the same argv and inspect stdout — anything that isn't the JSON document must go to stderr.
  2. Move debug/logging in the parser to stderr (e.g. console.error / print(..., file=sys.stderr)).
  3. Increase parser.maxBuffer if the output was truncated (a parse error near the end often indicates truncation).
  4. Confirm the parser exits 0; a non-zero exit with stdout text will still hit JSON.parse and fail here.

Example fix

# before (parsers/acme.py)
import sys, json
print('starting parse')  # pollutes stdout
print(json.dumps(jobs))

# after
import sys, json
print('starting parse', file=sys.stderr)  # logs go to stderr
print(json.dumps(jobs))    # stdout is pure JSON
Defensive patterns

Strategy: try-catch

Validate before calling

// Before trusting parser output, you can pre-validate by invoking the parser
// in a dry-run and checking stdout parses as JSON.
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
export async function parserEmitsJson(command, args) {
  try {
    const { stdout } = await execFileAsync(command, args, { timeout: 5000, maxBuffer: 1 << 20 });
    JSON.parse(stdout); return true;
  } catch { return false; }
}

Type guard

/** @param {string} stdout */
function isJsonDocument(stdout) {
  try { JSON.parse(stdout); return true; } catch { return false; }
}

Try / catch

try {
  const jobs = await provider.fetch(entry, ctx);
  results.push(...jobs);
} catch (err) {
  if (err.message === 'local parser returned invalid JSON') {
    console.warn(`parser ${entry.name} emitted non-JSON stdout — check for stray logs/truncation`);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/ee49ecfd76ea1b08. Report an issue: GitHub.