{"record":{"id":"7bba2a189264c3da","repo":"mvanhorn/last30days-skill","slug":"label-path-must-be-a-top-level-json-object-go","errorCode":null,"errorMessage":"{label} {path} must be a top-level JSON object, got {type(payload).__name__}.","messagePattern":"(.+?) (.+?) must be a top-level JSON object, got (.+?)\\.","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":408,"sourceCode":") -> tuple[dict[str, Any], str, Any]:\n    \"\"\"Shared strict top-level validation for the two engine-written handoff\n    files (nominations bundle, pending report): readable, valid JSON object,\n    right kind and schema version, bundle_id present, within TTL. Returns\n    (payload, bundle_id, generated_at).\"\"\"\n    try:\n        raw = path.read_text(encoding=\"utf-8\")\n    except OSError as exc:\n        raise HandoffContractError(\n            f\"Could not read {label.lower()} {path}: {exc}\"\n        ) from exc\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise HandoffContractError(\n            f\"{label} {path} is not valid JSON: {exc}\"\n        ) from exc\n    if not isinstance(payload, dict):\n        raise HandoffContractError(\n            f\"{label} {path} must be a top-level JSON object, \"\n            f\"got {type(payload).__name__}.\"\n        )\n    version = payload.get(\"schema_version\")\n    if version != schema_version:\n        raise HandoffContractError(\n            f\"{label} {path} has schema version {version!r}; this \"\n            f\"build reads {schema_version!r}. {remedy}\"\n        )\n    file_kind = payload.get(\"kind\")\n    if file_kind != kind:\n        raise HandoffContractError(\n            f\"{label} {path} has kind {file_kind!r}; expected \"\n            f\"{kind!r}. {remedy}\"\n        )\n    bundle_id = str(payload.get(\"bundle_id\") or \"\")\n    if not bundle_id:\n        raise HandoffContractError(","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L390-L426","documentation":"`_parse_handoff_envelope` raises HandoffContractError when `json.loads` succeeds but the result is not a Python dict — e.g. the file holds a top-level JSON array or scalar. The engine-written envelope contract is a JSON object with schema_version/kind/bundle_id/generated_at, so any other top-level type is a contract violation. The message reports the actual type name (list, str, int, ...).","triggerScenarios":"A `discover-nominations.json` replaced with a bare JSON array of nominations (dropping the envelope); a file containing just a string or number; a hand-authored attempt to reconstruct a bundle from its rows only.","commonSituations":"Users or agents 'simplifying' the bundle format; a tool re-serializing the file and losing the object wrapper; jq round-trips like `jq '.nominations' file > file` that extract a sub-array.","solutions":["Check the file: `python3 -c \"import json;print(type(json.load(open('<path>'))))\"` — it must be dict.","If you extracted/rewrote it, restore the full envelope object (schema_version, kind, bundle_id, generated_at, context, nominations) — but the reliable path is regeneration via `--discover --nominate-only`.","Never pipe jq filters that change the top level back over the handoff file."],"exampleFix":"# before\njq '.nominations' discover-nominations.json > discover-nominations.json   # now a top-level array\n# after: regenerate instead of editing\nrm discover-nominations.json && python3 last30days.py \"topic\" --discover --nominate-only --save-dir .","handlingStrategy":"type-guard","validationCode":"import json\nfrom pathlib import Path\n\ndef envelope_is_object(path: Path) -> bool:\n    try:\n        return isinstance(json.loads(path.read_text(encoding=\"utf-8\")), dict)\n    except (OSError, json.JSONDecodeError):\n        return False","typeGuard":"import json\nfrom typing import Any\n\ndef is_json_object(raw: str) -> bool:\n    try:\n        return isinstance(json.loads(raw), dict)\n    except json.JSONDecodeError:\n        return False","tryCatchPattern":"from lib import discovery_handoff\ntry:\n    bundle = discovery_handoff.load_nominations_bundle(save_dir=save_dir, config_dir=config_dir)\nexcept discovery_handoff.HandoffContractError as exc:\n    sys.exit(2)  # message names the actual top-level type; regenerate the file","preventionTips":["Never run jq filters that change the top-level shape over handoff files in place.","Treat handoff files as opaque engine state; any transformation voids the contract."],"tags":["discovery","handoff","json","schema","exit-code-2"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}