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

  1. Do not hand-author handoff files — regenerate with `--discover --nominate-only` so a fresh bundle_id is issued.
  2. Check `jq .bundle_id <path>`; if empty/missing the file is not salvageable as-is.
  3. 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

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


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/5e79bce83303bb19. Report an issue: GitHub.