{"record":{"id":"0fc84b4f4cea248c","repo":"nexu-io/open-design","slug":"review-does-not-contain-row-level-results","errorCode":null,"errorMessage":"review does not contain row-level results","messagePattern":"review does not contain row-level results","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/queue_pet_repairs.py","lineNumber":24,"sourceCode":"import argparse\nimport json\nimport shutil\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\n\ndef load_json(path: Path) -> dict[str, object]:\n    if not path.exists():\n        raise SystemExit(f\"file not found: {path}\")\n    return json.loads(path.read_text(encoding=\"utf-8\"))\n\n\ndef rows_to_repair(\n    review: dict[str, object], *, repair_on_warnings: bool\n) -> list[dict[str, object]]:\n    rows = review.get(\"rows\")\n    if not isinstance(rows, list):\n        raise SystemExit(\"review does not contain row-level results\")\n\n    repairs: list[dict[str, object]] = []\n    for row in rows:\n        if not isinstance(row, dict) or not isinstance(row.get(\"state\"), str):\n            continue\n        errors = row.get(\"errors\") if isinstance(row.get(\"errors\"), list) else []\n        warnings = row.get(\"warnings\") if isinstance(row.get(\"warnings\"), list) else []\n        if errors or (repair_on_warnings and warnings):\n            repairs.append(\n                {\n                    \"state\": row[\"state\"],\n                    \"reason\": \"; \".join(str(item) for item in [*errors, *warnings])\n                    or \"the row did not pass visual QA\",\n                }\n            )\n    return repairs\n\n","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/queue_pet_repairs.py#L6-L42","documentation":"Raised by rows_to_repair() in queue_pet_repairs.py when review.get('rows') is not a list. The repair loop depends on row-level QA results (each row carrying state/errors/warnings), so a review payload without a rows array is treated as a schema mismatch, not an empty repair set.","triggerScenarios":"Feeding a whole-run summary JSON (no per-row breakdown) as --review; the QA reviewer wrote a different top-level key (e.g. 'results' or 'frames'); the file is valid JSON but the wrong artifact entirely.","commonSituations":"QA pipeline changed its output schema between runs; reviewer emitted slide-level instead of row-level verdicts; user pointed --review at imagegen-jobs.json by mistake.","solutions":["Re-run the QA review so it emits a top-level rows: [...] array with per-row state/errors/warnings.","Inspect the review file: jq '.rows | type' review.json should report 'array'.","Confirm --review points at the row-level review artifact, not the manifest.","If the schema genuinely changed, transform the file into {\"rows\": [...]} before retrying."],"exampleFix":"# before: review.json = {\"overall\": \"fail\"}\n# after: review.json = {\"rows\": [{\"state\": \"idle\", \"errors\": [\"empty slot\"]}]}","handlingStrategy":"type-guard","validationCode":"rows = review.get(\"rows\")\nif not isinstance(rows, list):\n    raise SystemExit(\"review.json must have a top-level 'rows' array; got \" + type(rows).__name__)","typeGuard":"def is_row_review(payload: object) -> bool:\n    return isinstance(payload, dict) and isinstance(payload.get(\"rows\"), list)","tryCatchPattern":null,"preventionTips":["Freeze the QA review's output schema in a JSON schema and validate before repair.","Have the reviewer write a schema_version field so mismatches are detectable.","Unit-test rows_to_repair against a canonical review fixture."],"tags":["data-validation","cli","hatch-pet","schema"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}