{"record":{"id":"3a8cb027612ceae6","repo":"nexu-io/open-design","slug":"invalid-imagegen-jobs-json-jobs-must-be-a-list-3a8cb0","errorCode":null,"errorMessage":"invalid imagegen-jobs.json: jobs must be a list","messagePattern":"invalid imagegen-jobs\\.json: jobs must be a list","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/pet_job_status.py","lineNumber":21,"sourceCode":"\nfrom __future__ import annotations\n\nimport argparse\nimport json\nfrom pathlib import Path\n\n\ndef load_manifest(run_dir: Path) -> dict[str, object]:\n    path = run_dir / \"imagegen-jobs.json\"\n    if not path.exists():\n        raise SystemExit(f\"job manifest not found: {path}\")\n    return json.loads(path.read_text(encoding=\"utf-8\"))\n\n\ndef jobs(manifest: dict[str, object]) -> list[dict[str, object]]:\n    raw = manifest.get(\"jobs\")\n    if not isinstance(raw, list):\n        raise SystemExit(\"invalid imagegen-jobs.json: jobs must be a list\")\n    return [job for job in raw if isinstance(job, dict)]\n\n\ndef completed_ids(manifest: dict[str, object]) -> set[str]:\n    return {\n        str(job[\"id\"])\n        for job in jobs(manifest)\n        if job.get(\"status\") == \"complete\" and isinstance(job.get(\"id\"), str)\n    }\n\n\ndef missing_deps(job: dict[str, object], completed: set[str]) -> list[str]:\n    deps = job.get(\"depends_on\", [])\n    if not isinstance(deps, list):\n        return []\n    return [dep for dep in deps if isinstance(dep, str) and dep not in completed]\n\n","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/pet_job_status.py#L3-L39","documentation":"Raised by jobs() in pet_job_status.py (and the parallel check in generate_pet_images.py:51) when imagegen-jobs.json parses but its top-level jobs field is not a list. The contract is {schema_version, jobs: [...]}, so a non-list jobs value is treated as corruption.","triggerScenarios":"imagegen-jobs.json was hand-edited so jobs is a dict or object keyed by id; a partial write left jobs as null; a different schema version nested jobs elsewhere.","commonSituations":"Restructuring the manifest to {jobs: {idle: {...}}} instead of a list; truncated write from a crashed run; manifest produced by an incompatible tool.","solutions":["Open imagegen-jobs.json and confirm jobs is a JSON array of job objects.","If it is keyed by id, convert to a list, or regenerate with prepare_pet_run.py.","Validate JSON: python -c \"import json; d=json.load(open('imagegen-jobs.json')); print(type(d['jobs']).__name__)\" should print list.","Re-run the status/generation command."],"exampleFix":"// before (manifest)\n{\"jobs\": {\"base\": {...}, \"idle\": {...}}}\n\n// after\n{\"schema_version\": 1, \"jobs\": [{\"id\": \"base\", ...}, {\"id\": \"idle\", ...}]}","handlingStrategy":"type-guard","validationCode":"def assert_jobs_list(manifest: dict) -> list:\n    raw = manifest.get(\"jobs\")\n    if not isinstance(raw, list):\n        raise SystemExit(\"invalid imagegen-jobs.json: jobs must be a list\")\n    return raw","typeGuard":"from typing import Any\n\ndef has_jobs_list(manifest: Any) -> bool:\n    return isinstance(manifest, dict) and isinstance(manifest.get(\"jobs\"), list)","tryCatchPattern":null,"preventionTips":["Treat imagegen-jobs.json as generated; regenerate with prepare_pet_run.py instead of restructuring.","Validate with jsonschema: jobs must be an array of objects.","Keep schema_version bumped when the manifest shape changes."],"tags":["manifest","json-schema","validation","system-exit"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}