{"record":{"id":"527319469c4cef4a","repo":"nexu-io/open-design","slug":"unknown-job-id-job-id","errorCode":null,"errorMessage":"unknown job id: {job_id}","messagePattern":"unknown job id: (.+?)","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/derive_running_left_from_running_right.py","lineNumber":33,"sourceCode":"def 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 job_list(manifest: dict[str, object]) -> list[dict[str, object]]:\n    jobs = manifest.get(\"jobs\")\n    if not isinstance(jobs, list):\n        raise SystemExit(\"invalid imagegen-jobs.json: jobs must be a list\")\n    return [job for job in jobs if isinstance(job, dict)]\n\n\ndef find_job(manifest: dict[str, object], job_id: str) -> dict[str, object]:\n    for job in job_list(manifest):\n        if job.get(\"id\") == job_id:\n            return job\n    raise SystemExit(f\"unknown job id: {job_id}\")\n\n\ndef file_sha256(path: Path) -> str:\n    digest = hashlib.sha256()\n    with path.open(\"rb\") as file:\n        for chunk in iter(lambda: file.read(1024 * 1024), b\"\"):\n            digest.update(chunk)\n    return digest.hexdigest()\n\n\ndef image_metadata(path: Path) -> dict[str, object]:\n    with Image.open(path) as image:\n        image.verify()\n    with Image.open(path) as image:\n        return {\n            \"width\": image.width,\n            \"height\": image.height,\n            \"mode\": image.mode,","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/derive_running_left_from_running_right.py#L15-L51","documentation":"Thrown by find_job() in derive_running_left_from_running_right.py when no job in the manifest's jobs list has an 'id' equal to the requested job_id. The script looks up both 'running-right' and 'running-left', so either being absent triggers this.","triggerScenarios":"Run derive_running_left_from_running_right.py against a manifest whose jobs list does not contain an entry with id 'running-right' or 'running-left' — e.g. only idle/waving jobs are present, or ids are spelled differently (e.g. 'run-right').","commonSituations":"Running the derive step before the running jobs were added to the manifest; id naming drift between the pipeline and this script; a manifest from a pet variant that does not define running animation.","solutions":["Inspect the manifest: list every job id and confirm both 'running-right' and 'running-left' exist.","Run the imagegen steps that produce and register the running-right and running-left jobs before deriving.","If ids were renamed, align the manifest ids back to the canonical 'running-right' / 'running-left'."],"exampleFix":"// before\n{\"jobs\": [{\"id\": \"run-right\", ...}]}\n// after\n{\"jobs\": [{\"id\": \"running-right\", ...}, {\"id\": \"running-left\", \"mirror_policy\": {\"may_derive_from\": \"running-right\"}}]}","handlingStrategy":"validation","validationCode":"def assert_jobs_present(manifest, required_ids):\n    have = {j.get(\"id\") for j in manifest.get(\"jobs\", [])}\n    missing = [i for i in required_ids if i not in have]\n    if missing:\n        raise SystemExit(f\"manifest missing job ids: {missing}\")\n\nassert_jobs_present(manifest, [\"running-right\", \"running-left\"])","typeGuard":"def job_exists(manifest, job_id) -> bool:\n    return any(j.get(\"id\") == job_id for j in manifest.get(\"jobs\", []))","tryCatchPattern":null,"preventionTips":["Confirm both 'running-right' and 'running-left' job entries exist before deriving.","Keep job ids canonical; do not rename them per pet variant."],"tags":["manifest","hatch-pet","data-validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}