{"record":{"id":"ee43e5634d45a81c","repo":"mvanhorn/last30days-skill","slug":"label-path-has-schema-version-version-r-thi","errorCode":null,"errorMessage":"{label} {path} has schema version {version!r}; this build reads {schema_version!r}. {remedy}","messagePattern":"(.+?) (.+?) has schema version (.+?); this build reads (.+?)\\. (.+?)","errorType":"exception","errorClass":"HandoffContractError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/discovery_handoff.py","lineNumber":414,"sourceCode":"        raw = path.read_text(encoding=\"utf-8\")\n    except OSError as exc:\n        raise HandoffContractError(\n            f\"Could not read {label.lower()} {path}: {exc}\"\n        ) from exc\n    try:\n        payload = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        raise HandoffContractError(\n            f\"{label} {path} is not valid JSON: {exc}\"\n        ) from exc\n    if not isinstance(payload, dict):\n        raise HandoffContractError(\n            f\"{label} {path} must be a top-level JSON object, \"\n            f\"got {type(payload).__name__}.\"\n        )\n    version = payload.get(\"schema_version\")\n    if version != schema_version:\n        raise HandoffContractError(\n            f\"{label} {path} has schema version {version!r}; this \"\n            f\"build reads {schema_version!r}. {remedy}\"\n        )\n    file_kind = payload.get(\"kind\")\n    if file_kind != kind:\n        raise HandoffContractError(\n            f\"{label} {path} has kind {file_kind!r}; expected \"\n            f\"{kind!r}. {remedy}\"\n        )\n    bundle_id = str(payload.get(\"bundle_id\") or \"\")\n    if not bundle_id:\n        raise HandoffContractError(\n            f\"{label} {path} is missing its bundle_id; \"\n            f\"{missing_id_context}. {remedy}\"\n        )\n    generated_at = payload.get(\"generated_at\")\n    if not env.is_timestamp_fresh(generated_at, DISCOVERY_HANDOFF_TTL_SECONDS):\n        raise HandoffContractError(","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/discovery_handoff.py#L396-L432","documentation":"`_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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: bundle from old build, new build refuses it\npython3 last30days.py \"topic\" --discover --judgments judgments.json\n# after: regenerate at current schema, then judge\npython3 last30days.py \"topic\" --discover --nominate-only --save-dir /tmp/run && python3 last30days.py \"topic\" --discover --judgments judgments.json --save-dir /tmp/run","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\nfrom lib import schema\n\ndef bundle_version_ok(path: Path) -> bool:\n    try:\n        return json.loads(path.read_text(encoding=\"utf-8\")).get(\"schema_version\") == schema.DISCOVERY_NOMINATIONS_SCHEMA_VERSION\n    except (OSError, json.JSONDecodeError, AttributeError):\n        return False","typeGuard":null,"tryCatchPattern":"from lib import discovery_handoff\ntry:\n    bundle = discovery_handoff.load_nominations_bundle(save_dir=save_dir, config_dir=config_dir)\nexcept discovery_handoff.HandoffContractError as exc:\n    if \"schema version\" in exc.message:\n        # engine/build mismatch: re-run the producing leg with THIS build\n        ...","preventionTips":["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."],"tags":["discovery","handoff","schema-version","upgrade","exit-code-2"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}