{"record":{"id":"d22148bc0d69a806","repo":"shareAI-lab/learn-claude-code","slug":"goal-evaluator-must-return-a-json-object","errorCode":null,"errorMessage":"goal evaluator must return a JSON object","messagePattern":"goal evaluator must return a JSON object","errorType":"validation","errorClass":"GoalError","httpStatus":null,"severity":"error","filePath":"s17_goal_loop/code.py","lineNumber":185,"sourceCode":"        size += item_size\n    return \"\\n\\n\".join(reversed(selected))\n\n\ndef _parse_json_object(text: str) -> dict[str, Any]:\n    stripped = text.strip()\n    if stripped.startswith(\"```\"):\n        lines = stripped.splitlines()\n        if lines and lines[0].startswith(\"```\"):\n            lines = lines[1:]\n        if lines and lines[-1].strip() == \"```\":\n            lines = lines[:-1]\n        stripped = \"\\n\".join(lines).strip()\n    try:\n        value = json.loads(stripped)\n    except json.JSONDecodeError as error:\n        raise GoalError(\"goal evaluator returned invalid JSON\") from error\n    if not isinstance(value, dict):\n        raise GoalError(\"goal evaluator must return a JSON object\")\n    if not isinstance(value.get(\"ok\"), bool):\n        raise GoalError(\"goal evaluator response requires boolean 'ok'\")\n    if not isinstance(value.get(\"reason\"), str) or not value[\"reason\"].strip():\n        raise GoalError(\"goal evaluator response requires non-empty 'reason'\")\n    impossible = value.get(\"impossible\", False)\n    if not isinstance(impossible, bool):\n        raise GoalError(\"goal evaluator 'impossible' must be boolean\")\n    if value[\"ok\"] and impossible:\n        raise GoalError(\n            \"goal evaluator cannot return both ok and impossible\"\n        )\n    return {\n        \"ok\": value[\"ok\"],\n        \"reason\": value[\"reason\"].strip(),\n        \"impossible\": impossible,\n    }\n\n","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s17_goal_loop/code.py#L167-L203","documentation":"After successful json.loads, _parse_json_object requires the top-level value to be a dict; anything else (array, string, number) raises GoalError('goal evaluator must return a JSON object'). The downstream contract reads keys ok/reason/impossible, so a non-object reply cannot be interpreted.","triggerScenarios":"Evaluator returns a JSON array of findings or a quoted JSON string (double-encoded) instead of a bare object.","commonSituations":"Prompt asking the model to 'list reasons' (elicits an array); JSON.stringify'd strings passed through; schemas declaring type:array for the evaluator response.","solutions":["Fix the evaluator prompt/schema to demand a top-level object with ok/reason/impossible.","If the evaluator can only produce arrays, wrap its output into an object before it reaches _parse_json_object.","Return the literal object, not its serialized string, from custom evaluators."],"exampleFix":"# before: evaluator text = '[{\"ok\": true}]'  (array at top level)\n\n# after: evaluator text = '{\"ok\": true, \"reason\": \"tests pass\", \"impossible\": false}'","handlingStrategy":"type-guard","validationCode":"import json\ndef evaluator_returns_object(text: str) -> bool:\n    try:\n        return isinstance(json.loads(text.strip().strip('`')), dict)\n    except json.JSONDecodeError:\n        return False","typeGuard":"def is_evaluator_object(text: str) -> bool:\n    stripped = text.strip()\n    if stripped.startswith('```'):\n        stripped = stripped.splitlines()[1]\n    try:\n        return isinstance(json.loads(stripped), dict)\n    except json.JSONDecodeError:\n        return False","tryCatchPattern":"try:\n    ev = _parse_json_object(text)\nexcept GoalError as e:\n    if \"must return a JSON object\" in str(e):\n        ev = _parse_json_object('{\"ok\": false, \"reason\": ' + json.dumps(text) + '}')  # treat as block-with-reason\n    else:\n        raise","preventionTips":["Prompt for the exact object shape {ok, reason, impossible} with an example.","Never ask the evaluator to 'list' things at top level; findings belong under a key.","Return objects (not serialized strings) from custom evaluator implementations."],"tags":["goal-loop","evaluator","json","validation","llm"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}