mvanhorn/last30days-skill · error · HandoffContractError
{label} {path} must be a top-level JSON object, got {type(pa
Error message
{label} {path} must be a top-level JSON object, got {type(payload).__name__}. What it means
`_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, ...).
Source
Thrown at skills/last30days/scripts/lib/discovery_handoff.py:408
) -> tuple[dict[str, Any], str, Any]:
"""Shared strict top-level validation for the two engine-written handoff
files (nominations bundle, pending report): readable, valid JSON object,
right kind and schema version, bundle_id present, within TTL. Returns
(payload, bundle_id, generated_at)."""
try:
raw = path.read_text(encoding="utf-8")
except OSError as exc:
raise HandoffContractError(
f"Could not read {label.lower()} {path}: {exc}"
) from exc
try:
payload = json.loads(raw)
except json.JSONDecodeError as exc:
raise HandoffContractError(
f"{label} {path} is not valid JSON: {exc}"
) from exc
if not isinstance(payload, dict):
raise HandoffContractError(
f"{label} {path} must be a top-level JSON object, "
f"got {type(payload).__name__}."
)
version = payload.get("schema_version")
if version != schema_version:
raise HandoffContractError(
f"{label} {path} has schema version {version!r}; this "
f"build reads {schema_version!r}. {remedy}"
)
file_kind = payload.get("kind")
if file_kind != kind:
raise HandoffContractError(
f"{label} {path} has kind {file_kind!r}; expected "
f"{kind!r}. {remedy}"
)
bundle_id = str(payload.get("bundle_id") or "")
if not bundle_id:
raise HandoffContractError(View on GitHub (pinned to c7460f6114)
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.
Example fix
# before jq '.nominations' discover-nominations.json > discover-nominations.json # now a top-level array # after: regenerate instead of editing rm discover-nominations.json && python3 last30days.py "topic" --discover --nominate-only --save-dir .
Defensive patterns
Strategy: type-guard
Validate before calling
import json
from pathlib import Path
def envelope_is_object(path: Path) -> bool:
try:
return isinstance(json.loads(path.read_text(encoding="utf-8")), dict)
except (OSError, json.JSONDecodeError):
return False Type guard
import json
from typing import Any
def is_json_object(raw: str) -> bool:
try:
return isinstance(json.loads(raw), dict)
except json.JSONDecodeError:
return False Try / catch
from lib import discovery_handoff
try:
bundle = discovery_handoff.load_nominations_bundle(save_dir=save_dir, config_dir=config_dir)
except discovery_handoff.HandoffContractError as exc:
sys.exit(2) # message names the actual top-level type; regenerate the file Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Nominations bundle {path} must carry a top-level "nomination
- Pending discovery report {path} must carry a top-level \"rep
- {label} {path} is not valid JSON: {exc}
- {label} {path} has kind {file_kind!r}; expected {kind!r}. {r
- {label} {path} is missing its bundle_id; {missing_id_context
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/7bba2a189264c3da.
Report an issue: GitHub.