mvanhorn/last30days-skill · error · HandoffContractError
{label} {path} is missing its bundle_id; {missing_id_context
Error message
{label} {path} is missing its bundle_id; {missing_id_context}. {remedy} What it means
`_parse_handoff_envelope` raises HandoffContractError when the payload object has no non-empty `bundle_id`. The bundle_id is the join key of the whole three-leg protocol: leg-2 host judgments and the leg-3 pending report must bind to the exact bundle the sweep produced, so an envelope without one cannot participate. The message includes per-artifact context ('angles cannot bind to it' for pending reports) and the matching remedy.
Source
Thrown at skills/last30days/scripts/lib/discovery_handoff.py:426
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(
f"{label} {path} is missing its bundle_id; "
f"{missing_id_context}. {remedy}"
)
generated_at = payload.get("generated_at")
if not env.is_timestamp_fresh(generated_at, DISCOVERY_HANDOFF_TTL_SECONDS):
raise HandoffContractError(
f"{label} {path} is stale (generated_at="
f"{generated_at!r}, TTL {int(DISCOVERY_HANDOFF_TTL_SECONDS)}s): "
f"{stale_context}. {remedy}"
)
return payload, bundle_id, generated_at
def _parse_bundle_file(path: Path) -> NominationsBundle:
payload, bundle_id, generated_at = _parse_handoff_envelope(
path,
label="Nominations bundle",
kind=schema.DISCOVERY_NOMINATIONS_KIND,View on GitHub (pinned to c7460f6114)
Solutions
- Do not hand-author handoff files — regenerate with `--discover --nominate-only` so a fresh bundle_id is issued.
- Check `jq .bundle_id <path>`; if empty/missing the file is not salvageable as-is.
- After regenerating, re-author judgments: their bundle_id must match the NEW bundle's id (leg 2 reads the bundle and validates binding).
Example fix
# before: hand-built bundle lacking bundle_id # after python3 last30days.py "topic" --discover --nominate-only --save-dir /tmp/run # engine writes bundle_id like a1b2c3d4e5f6a7b8
Defensive patterns
Strategy: validation
Validate before calling
import json
from pathlib import Path
def has_bundle_id(path: Path) -> bool:
try:
return bool(json.loads(path.read_text(encoding="utf-8")).get("bundle_id"))
except (OSError, 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:
if "bundle_id" in exc.message:
# unbindable state: regenerate the bundle and re-author judgments
... Prevention
- Always take the bundle_id from the engine-written bundle (jq .bundle_id) when authoring judgments.
- Never reconstruct a bundle by hand — the id is a 16-hex-char engine-issued token, not derivable.
When it happens
Trigger: A hand-edited or reconstructed handoff file where `bundle_id` was dropped, set to null, or set to ""' — for example rebuilding a 'similar' bundle from a template, or a jq rewrite that lost the field.
Common situations: Attempting to reuse a previous run's bundle by editing its contents; partial corruption that kept valid JSON but shed fields; a model/host authoring a bundle by hand instead of running leg 1.
Related errors
- {label} {path} must be a top-level JSON object, got {type(pa
- {label} {path} has kind {file_kind!r}; expected {kind!r}. {r
- Nominations bundle {path} must carry a top-level "nomination
- Pending discovery report {path} must carry a top-level \"rep
- No handoff location available to write the nominations bundl
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/5e79bce83303bb19.
Report an issue: GitHub.