{"record":{"id":"3b173a4076733b6d","repo":"Yeachan-Heo/oh-my-codex","slug":"evaluator-output-must-be-a-json-object","errorCode":null,"errorMessage":"Evaluator output must be a JSON object.","messagePattern":"Evaluator output must be a JSON object\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/autoresearch/contracts.ts","lineNumber":195,"sourceCode":"    evaluator: {\n      command,\n      format: 'json',\n      ...(keepPolicy ? { keep_policy: keepPolicy } : {}),\n    },\n    body,\n  };\n}\n\nexport function parseEvaluatorResult(raw: string): AutoresearchEvaluatorResult {\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(raw);\n  } catch {\n    throw contractError('Evaluator output must be valid JSON with required boolean pass and optional numeric score.');\n  }\n\n  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw contractError('Evaluator output must be a JSON object.');\n  }\n\n  const result = parsed as Record<string, unknown>;\n  if (typeof result.pass !== 'boolean') {\n    throw contractError('Evaluator output must include boolean pass.');\n  }\n  if (result.score !== undefined && typeof result.score !== 'number') {\n    throw contractError('Evaluator output score must be numeric when provided.');\n  }\n\n  return {\n    pass: result.pass,\n    ...(result.score === undefined ? {} : { score: result.score }),\n  };\n}\n\nexport async function loadAutoresearchMissionContract(missionDirArg: string): Promise<AutoresearchMissionContract> {\n  const missionDir = resolve(missionDirArg);","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/autoresearch/contracts.ts#L177-L213","documentation":"parseEvaluatorResult throws when the evaluator output parses as valid JSON but is not a plain object — e.g. an array, a number, a string, or null. The contract requires a top-level JSON object so that pass/score fields can be read.","triggerScenarios":"Evaluator prints '[true]', '42', '\"pass\"', or 'null' — valid JSON but not an object; the !parsed || typeof !== 'object' || Array.isArray guard rejects it.","commonSituations":"Evaluators that print a bare score number or a JSON array of results; printing 'null' on no-op runs.","solutions":["Print a JSON object: '{\"pass\": true}' minimum.","Wrap array results as an object field (e.g. {\"pass\": true, \"results\": [...]}) with a top-level boolean pass.","Ensure the final stdout line/string is the object itself."],"exampleFix":"# before\necho '[{\"pass\":true}]'\n\n# after\necho '{\"pass\":true}'","handlingStrategy":"type-guard","validationCode":"function evaluatorOutputIsJsonObject(raw: string): boolean {\n  try {\n    const v = JSON.parse(raw);\n    return !!v && typeof v === 'object' && !Array.isArray(v);\n  } catch { return false; }\n}","typeGuard":"const isJsonObject = (v: unknown): v is Record<string, unknown> =>\n  !!v && typeof v === 'object' && !Array.isArray(v);","tryCatchPattern":"try {\n  parseEvaluatorResult(stdout);\n} catch (err) {\n  if ((err as Error).message.includes('must be a JSON object')) {\n    // wrap arrays/scalars into an object with a boolean pass field\n  }\n  throw err;\n}","preventionTips":["Always emit a top-level object with a pass boolean.","Use JSON.stringify({ pass, score }) rather than hand-built strings."],"tags":["evaluator","json","shape-validation"],"backgroundTag":"json-shape-mismatch","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}