{"record":{"id":"5ac5efddc46e0473","repo":"mvanhorn/last30days-skill","slug":"pending-discovery-report-path-must-carry-a-top-l","errorCode":null,"errorMessage":"Pending discovery report {path} must carry a top-level \\\"report\\\" object. {_RESUME_REMEDY}","messagePattern":"Pending discovery report (.+?) must carry a top-level \\\\\"report\\\\\" object\\. (.+?)","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":587,"sourceCode":"            f\"{_searched_lines(searched)}\\n{_RESUME_REMEDY}\"\n        )\n    return _parse_pending_file(path)\n\n\ndef _parse_pending_file(path: Path) -> PendingReport:\n    payload, bundle_id, generated_at = _parse_handoff_envelope(\n        path,\n        label=\"Pending discovery report\",\n        kind=schema.DISCOVERY_PENDING_KIND,\n        schema_version=schema.DISCOVERY_PENDING_SCHEMA_VERSION,\n        remedy=_RESUME_REMEDY,\n        missing_id_context=\"angles cannot bind to it\",\n        stale_context=\"the judged window it captured has moved on\",\n    )\n    version = payload.get(\"schema_version\")\n    report = payload.get(\"report\")\n    if not isinstance(report, dict):\n        raise HandoffContractError(\n            f\"Pending discovery report {path} must carry a top-level \"\n            f\"\\\"report\\\" object. {_RESUME_REMEDY}\"\n        )\n    # Lenient per row (engine-written, but one corrupt row must not discard\n    # the rest): keep only well-shaped angle-input entries.\n    angle_inputs_raw = payload.get(\"angle_inputs\")\n    angle_inputs = {\n        str(nomination_id): {\n            str(key): str(value) for key, value in info.items()\n        }\n        for nomination_id, info in (\n            angle_inputs_raw.items() if isinstance(angle_inputs_raw, dict) else ()\n        )\n        if isinstance(info, dict)\n    }\n    return PendingReport(\n        schema_version=str(version),\n        bundle_id=bundle_id,","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L569-L605","documentation":"`_parse_pending_file` raises HandoffContractError when the pending report's envelope is valid but its top-level `report` value is not an object. The `report` key carries the leg-2-authored discovery report that `--discover --finalize` stamps angles onto and emits; without a well-shaped report there is nothing to finalize. The remedy is the resume remedy: re-run leg 2, or restart the protocol if the bundle is also stale. Note `angle_inputs` is parsed leniently (missing/ill-shaped degrades to empty), but `report` is strict.","triggerScenarios":"`discover-pending.json` edited so `report` became an array/string/null (e.g. `jq '.report |= [...]'` or `del(.report)` then re-adding wrong); a partially-written pending file from an interrupted leg 2; hand-construction of a pending report.","commonSituations":"Users trimming the pending file to 'reduce size'; sync-tool partial uploads; crash mid-write in leg 2; attempting to hand-forge leg-3 input.","solutions":["Check `jq '.report | type' <path>` — must be 'object'.","Re-run the resume leg (`--discover --judgments <file>`) to regenerate the pending report at full fidelity; hand-repair of the report object is not supported.","Keep `--save-dir` consistent across legs 2 and 3 so the correct pending file is found."],"exampleFix":"# before: jq 'del(.report)' accidentally applied to discover-pending.json\n# after: re-run leg 2 in the same save dir\npython3 last30days.py \"topic\" --discover --judgments judgments.json --save-dir /tmp/run","handlingStrategy":"type-guard","validationCode":"import json\nfrom pathlib import Path\n\ndef pending_has_report_object(path: Path) -> bool:\n    try:\n        return isinstance(json.loads(path.read_text(encoding=\"utf-8\")).get(\"report\"), dict)\n    except (OSError, json.JSONDecodeError):\n        return False","typeGuard":"def report_is_object(payload: dict) -> bool:\n    return isinstance(payload.get(\"report\"), dict)","tryCatchPattern":"from lib import discovery_handoff\ntry:\n    pending = discovery_handoff.load_pending_report(save_dir=save_dir, config_dir=config_dir)\nexcept discovery_handoff.HandoffContractError as exc:\n    if '\"report\" object' in exc.message:\n        # strict field: regenerate by re-running leg 2\n        ...","preventionTips":["Never trim keys out of discover-pending.json to shrink it; regenerate instead.","Let leg 2 write the file to completion; interrupting it mid-write can damage the report object."],"tags":["discovery","handoff","json","schema","exit-code-2"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}