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
- Run each leg with its own flags and let the engine write the files — never rename or repurpose handoff artifacts.
- Delete the wrong-kind file from the save/config dir and regenerate via the proper leg (`--discover --nominate-only` for the bundle).
- 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
- Never rename one handoff artifact to another's filename.
- Let each leg write its own file; the file names (discover-nominations.json / discover-pending.json) are fixed.
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
- No discovery nominations bundle found. Searched:\n{_searched
- {label} {path} must be a top-level JSON object, got {type(pa
- {label} {path} is missing its bundle_id; {missing_id_context
- Nominations bundle {path} must carry a top-level "nomination
- No pending discovery report found. Searched:\n{_searched_lin
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/d4ad4d0d7360ebad.
Report an issue: GitHub.