Yeachan-Heo/oh-my-codex · error · Error

autoresearch candidate artifact must be valid JSON

Error message

autoresearch candidate artifact must be valid JSON

What it means

parseAutoresearchCandidateArtifact JSON.parses the raw candidate artifact string and throws when parsing fails. The candidate artifact emitted by an autoresearch worker must be syntactically valid JSON before any field validation happens.

Source

Thrown at src/autoresearch/runtime.ts:1025

    instructionsFile: manifest.instructions_file,
    manifestFile: manifest.manifest_file,
    ledgerFile: manifest.ledger_file,
    latestEvaluatorFile: manifest.latest_evaluator_file,
    resultsFile: manifest.results_file,
    stateFile: activeRunStateFile(projectRoot),
    candidateFile: manifest.candidate_file,
    repoRoot: manifest.repo_root,
    worktreePath: manifest.worktree_path,
    taskDescription: `autoresearch resume ${runId}`,
  };
}

export function parseAutoresearchCandidateArtifact(raw: string): AutoresearchCandidateArtifact {
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch {
    throw new Error('autoresearch candidate artifact must be valid JSON');
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error('autoresearch candidate artifact must be a JSON object');
  }
  const record = parsed as Record<string, unknown>;
  const status = record.status;
  if (status !== 'candidate' && status !== 'noop' && status !== 'abort' && status !== 'interrupted') {
    throw new Error('autoresearch candidate artifact status must be candidate|noop|abort|interrupted');
  }
  if (record.candidate_commit !== null && typeof record.candidate_commit !== 'string') {
    throw new Error('autoresearch candidate artifact candidate_commit must be string|null');
  }
  if (typeof record.base_commit !== 'string' || !record.base_commit.trim()) {
    throw new Error('autoresearch candidate artifact base_commit is required');
  }
  if (typeof record.description !== 'string') {
    throw new Error('autoresearch candidate artifact description is required');
  }

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Print/inspect the raw artifact string and find the JSON syntax error location
  2. Strip any surrounding markdown fences or prose so the string is pure JSON
  3. Ensure the artifact is fully flushed before reading it (write-then-rename or wait for worker exit)
  4. If the worker persistently emits non-JSON, fix the worker prompt/template to require raw JSON only

Example fix

// before
const artifact = parseAutoresearchCandidateArtifact(rawLlmOutput); // raw contains ```json fences
// after
const json = rawLlmOutput.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
const artifact = parseAutoresearchCandidateArtifact(json);
Defensive patterns

Strategy: type-guard

Validate before calling

function isParsableJson(s: string): boolean {
  try { JSON.parse(s); return true; } catch { return false; }
}
if (!isParsableJson(raw)) throw new Error('Candidate artifact is not valid JSON; check worker output for fences/truncation.');

Type guard

function parseJsonSafe(s: string): unknown | undefined {
  try { return JSON.parse(s); } catch { return undefined; }
}

Try / catch

try { const a = parseAutoresearchCandidateArtifact(raw); }
catch (e) { if (e instanceof Error && e.message === 'autoresearch candidate artifact must be valid JSON') { /* strip fences, log raw excerpt, re-ask worker */ } throw e; }

Prevention

When it happens

Trigger: Passing a candidate artifact string that is not parseable JSON: truncated output, markdown fences around JSON, an empty string, or a worker writing free-form text to the artifact file.

Common situations: LLM worker output with prose or code fences around the JSON, truncated stdout captured to a file, encoding issues/BOM, or a partially written artifact read while the worker was still writing it.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/571cd5153cb62e0a. Report an issue: GitHub.