Hmbown/CodeWhale · error · PersistenceBacklogError
receipt schema_version changed
Error message
receipt schema_version changed
What it means
Raised in validate_receipt (scripts/check-persistence-backlog-budget.py:149-150) when receipt["schema_version"] != 2 (SCHEMA_VERSION). The version gate ensures the receipt layout matches what this checker knows how to interpret before any field is trusted.
Source
Thrown at scripts/check-persistence-backlog-budget.py:150
"sample_count": 1,
}
def validate_receipt(
receipt: dict[str, Any],
*,
expected_source: dict[str, Any] | None = None,
require_clean_source: bool = False,
) -> None:
missing = [field for field in REQUIRED_RECEIPT_FIELDS if field not in receipt]
if missing:
raise PersistenceBacklogError(
"receipt missing required field(s): " + ", ".join(missing)
)
if receipt["document_kind"] != RECEIPT_KIND:
raise PersistenceBacklogError(f"receipt document_kind must be {RECEIPT_KIND}")
if receipt["schema_version"] != SCHEMA_VERSION:
raise PersistenceBacklogError("receipt schema_version changed")
for field, expected in FIXTURE.items():
validate_frozen_field(field, receipt[field], expected)
if not isinstance(receipt["source_sha"], str) or not SOURCE_SHA_PATTERN.fullmatch(
receipt["source_sha"]
):
raise PersistenceBacklogError("receipt source_sha must be an exact lowercase Git SHA")
if type(receipt["source_dirty"]) is not bool:
raise PersistenceBacklogError("receipt source_dirty must be boolean")
for field, prefix in (("rustc_version", "rustc "), ("cargo_version", "cargo ")):
if not isinstance(receipt[field], str) or not receipt[field].startswith(prefix):
raise PersistenceBacklogError(f"receipt {field} must be a version string")
validate_frozen_field("build_profile", receipt["build_profile"], "test")
validate_frozen_field("sample_count", receipt["sample_count"], 1)
if expected_source is not None:
for field in (
"source_sha",
"source_dirty",
"rustc_version",View on GitHub (pinned to 8880682c63)
Solutions
- Regenerate the receipt (and baseline receipt) with the current measure script after any schema bump.
- When bumping SCHEMA_VERSION, update checker, Rust emitter, budget, and both receipts in one atomic commit.
- Discard receipts whose schema_version predates the current gate.
Example fix
// receipt (before) "schema_version": 1 // receipt (after) "schema_version": 2
Defensive patterns
Strategy: type-guard
Validate before calling
if receipt.get("schema_version") != 2:
sys.exit("receipt schema is not v2; regenerate with the current measure script") Type guard
def is_current_schema(doc: dict) -> bool:
return doc.get("schema_version") == 2 Try / catch
try:
validate_receipt(receipt)
except PersistenceBacklogError as e:
if "schema_version changed" in str(e):
raise RuntimeError("schema migration incomplete: regenerate receipt and baseline") from e
raise Prevention
- Bump SCHEMA_VERSION, emitter, budget, and both receipts atomically.
- Regenerate every receipt artifact immediately after a version bump.
- Refuse to load receipts whose schema_version predates the checker.
When it happens
Trigger: Checking a v1 receipt with a v2 checker after bumping SCHEMA_VERSION; regenerating the budget but reusing an old receipt; a draft schema bump where only one side was updated.
Common situations: Schema migrations where receipts, baseline, budget, and checker must move in lockstep; long-lived receipts checked out alongside updated tooling.
Related errors
- ${names.join(" or ")} is required
- Codewhale terminal receipt exceeded its total bound
- Codewhale stream-json line {line_number} was not an object
- invalid {label} {path}: {error}
- {label} must be a JSON object
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/71f2cbcf96580d7b.
Report an issue: GitHub.