mvanhorn/last30days-skill · error · HandoffContractError
{label} {path} has schema version {version!r}; this build re
Error message
{label} {path} has schema version {version!r}; this build reads {schema_version!r}. {remedy} What it means
`_parse_handoff_envelope` raises HandoffContractError on schema-version mismatch: the file's `schema_version` field differs from the version this build reads (`DISCOVERY_NOMINATIONS_SCHEMA_VERSION` for bundles, `DISCOVERY_PENDING_SCHEMA_VERSION` for pending reports). The message shows both versions and the appropriate remedy (re-sweep for bundles, resume leg for pending reports). This guards against reading state written by an older/newer engine with an incompatible envelope.
Source
Thrown at skills/last30days/scripts/lib/discovery_handoff.py:414
raw = path.read_text(encoding="utf-8")
except OSError as exc:
raise HandoffContractError(
f"Could not read {label.lower()} {path}: {exc}"
) from exc
try:
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(View on GitHub (pinned to c7460f6114)
Solutions
- Re-run the producing leg with the CURRENT engine so state is rewritten at the version it reads: `--discover --nominate-only` for a stale-version bundle, `--discover --judgments <file>` for a stale pending report.
- If both versions of the skill are installed, give each its own `--save-dir` so their handoff state never mixes.
- Clear stale state from the config dir after upgrades: remove `discover-nominations.json` / `discover-pending.json` before starting a fresh protocol.
- Confirm which engine a harness invokes (beta installs as `/last30days-beta`) and stick with it for all three legs.
Example fix
# before: bundle from old build, new build refuses it python3 last30days.py "topic" --discover --judgments judgments.json # after: regenerate at current schema, then judge python3 last30days.py "topic" --discover --nominate-only --save-dir /tmp/run && python3 last30days.py "topic" --discover --judgments judgments.json --save-dir /tmp/run
Defensive patterns
Strategy: validation
Validate before calling
import json
from pathlib import Path
from lib import schema
def bundle_version_ok(path: Path) -> bool:
try:
return json.loads(path.read_text(encoding="utf-8")).get("schema_version") == schema.DISCOVERY_NOMINATIONS_SCHEMA_VERSION
except (OSError, json.JSONDecodeError, AttributeError):
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 "schema version" in exc.message:
# engine/build mismatch: re-run the producing leg with THIS build
... Prevention
- Finish a discovery protocol with one engine version; upgrade between protocols, not between legs.
- Give stable and beta skill installs separate --save-dir stores.
- Clear discover-*.json from the config dir after upgrading before starting a new protocol.
When it happens
Trigger: A bundle written by an older last30days install (before a schema bump) read by a newer build, or vice versa; two skill versions installed side by side (stable + beta private repo) sharing one config dir; upgrading the skill mid-protocol between legs.
Common situations: Running `/last30days-beta` and `/last30days` against the same `~/.config/last30days/`; upgrading the skill between leg 1 and leg 2; CI caching an old config dir across engine version changes.
Related errors
- No handoff location available to write the nominations bundl
- Could not write nominations bundle {path}: {exc}
- No discovery nominations bundle found. Searched:\n{_searched
- Could not read {label.lower()} {path}: {exc}
- {label} {path} is not valid JSON: {exc}
AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15).
Data as JSON: /api/errors/ee43e5634d45a81c.
Report an issue: GitHub.