mvanhorn/last30days-skill · error · HandoffContractError

{label} {path} has kind {file_kind!r}; expected {kind!r}. {r

Error message

{label} {path} has kind {file_kind!r}; expected {kind!r}. {remedy}

What it means

`_parse_handoff_envelope` raises HandoffContractError when the file's `kind` field does not equal the expected discriminator (`DISCOVERY_NOMINATIONS_KIND` for `discover-nominations.json`, `DISCOVERY_PENDING_KIND` for `discover-pending.json`). The kind check exists so a structurally valid handoff file of the wrong type is never parsed as another type — e.g. a pending report renamed/placed where a bundle is expected.

Source

Thrown at skills/last30days/scripts/lib/discovery_handoff.py:420

        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(
            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

View on GitHub (pinned to c7460f6114)

Solutions

  1. Run each leg with its own flags and let the engine write the files — never rename or repurpose handoff artifacts.
  2. Delete the wrong-kind file from the save/config dir and regenerate via the proper leg (`--discover --nominate-only` for the bundle).
  3. Inspect `jq .kind <path>` to confirm which artifact you actually have before deciding the next leg.

Example fix

# before
mv discover-pending.json discover-nominations.json   # trying to fake leg-1 state
# after
rm discover-nominations.json && python3 last30days.py "topic" --discover --nominate-only --save-dir .
Defensive patterns

Strategy: validation

Validate before calling

import json
from pathlib import Path
from lib import schema

def kind_matches(path: Path, expected_kind: str) -> bool:
    try:
        return json.loads(path.read_text(encoding="utf-8")).get("kind") == expected_kind
    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 "has kind" in exc.message:
        # wrong artifact in place: delete it and regenerate with the correct leg
        ...

Prevention

When it happens

Trigger: Renaming `discover-pending.json` to `discover-nominations.json` (or vice versa); a save dir carrying state from the wrong leg; a hand-crafted file copied from a different protocol artifact.

Common situations: Users trying to skip leg 1 by converting leg-2 output into a bundle; file juggling between save dirs; copy-paste of handoff files between protocol runs.

Related errors


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