mvanhorn/last30days-skill · error · HandoffContractError
Nominations bundle {path} must carry a top-level "nomination
Error message
Nominations bundle {path} must carry a top-level "nominations" list, got {type(rows_raw).__name__}. {_RESWEEP_REMEDY} What it means
`_parse_bundle_file` raises HandoffContractError when the bundle's top-level `nominations` value is not a JSON array. After the shared envelope checks pass, the reader requires a list of nomination rows; anything else (dict keyed by nomination id, null, string) is a corrupted pool. Per-row handling is deliberately lenient after this point, but the top-level shape is strict. Message includes the actual type name and the re-sweep remedy.
Source
Thrown at skills/last30days/scripts/lib/discovery_handoff.py:462
kind=schema.DISCOVERY_NOMINATIONS_KIND,
schema_version=schema.DISCOVERY_NOMINATIONS_SCHEMA_VERSION,
remedy=_RESWEEP_REMEDY,
missing_id_context="judgments cannot bind to it",
stale_context="the momentum window it captured has moved on",
)
version = payload.get("schema_version")
context = payload.get("context") or {}
boundary = context.get("enrichment_source_boundary")
requested = context.get("requested_sources")
try:
lookback_days = int(context.get("lookback_days") or 30)
except (TypeError, ValueError):
lookback_days = 30
rows_raw = payload.get("nominations")
if not isinstance(rows_raw, list):
raise HandoffContractError(
f"Nominations bundle {path} must carry a top-level "
f"\"nominations\" list, got {type(rows_raw).__name__}. "
f"{_RESWEEP_REMEDY}"
)
nominations: list[BundleNomination] = []
for position, row in enumerate(rows_raw, start=1):
# Lenient per row: the bundle is engine-written, but one corrupted
# row must not discard the rest of the pool.
if not isinstance(row, dict):
_warn(
f"skipping malformed nomination row {position} in "
f"{path.name} (not an object)"
)
continue
try:
nomination = pipeline.Nomination(
**schema.nomination_kwargs_from_dict(row.get("nomination") or {})View on GitHub (pinned to c7460f6114)
Solutions
- Verify with `jq '.nominations | type' <path>` — must be 'array'.
- If you must filter, keep the array shape: `jq '.nominations |= map(select(.heuristic_junk != true))' file > tmp && mv tmp file` — but regeneration is safer.
- When in doubt, re-run `--discover --nominate-only`; the engine never writes a non-list pool, so this shape always means the file was altered.
Example fix
# before
jq '.nominations |= { (.nomination_id): . }' discover-nominations.json > tmp && mv tmp discover-nominations.json # now an object
# after (keep array, or regenerate)
jq '.nominations |= map(select(.heuristic_junk != true))' discover-nominations.json > tmp && mv tmp discover-nominations.json Defensive patterns
Strategy: type-guard
Validate before calling
import json
from pathlib import Path
def nominations_is_list(path: Path) -> bool:
try:
return isinstance(json.loads(path.read_text(encoding="utf-8")).get("nominations"), list)
except (OSError, json.JSONDecodeError):
return False Type guard
def is_nomination_pool(value) -> bool:
return isinstance(value, list) and all(isinstance(row, dict) for row in value) 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:
if '"nominations" list' in exc.message:
# reshaped pool: re-sweep, do not repair
... Prevention
- When filtering a bundle with jq, keep the array shape (|= map(select(...))) and never convert to an object.
- Prefer encoding junk/pruning decisions in the judgments file instead of editing the bundle.
When it happens
Trigger: `nominations` hand-converted to an object keyed by nomination_id; set to null or missing via a jq rewrite (`jq 'del(.nominations)'`); a partial rewrite that replaced the list with a placeholder string.
Common situations: Editing bundles to remove unwanted nominations by restructuring instead of filtering the array in place; model-assisted 'cleanup' of the file; sync/merge damage to that key.
Related errors
- {label} {path} must be a top-level JSON object, got {type(pa
- 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/d2d403e65e068d71.
Report an issue: GitHub.