Hmbown/CodeWhale · error · PersistenceBacklogError
receipt source_sha must be an exact lowercase Git SHA
Error message
receipt source_sha must be an exact lowercase Git SHA
What it means
`receipt.source_sha` must be a string of exactly 40 lowercase hex characters matching `SOURCE_SHA_PATTERN = re.compile(r"[0-9a-f]{40}")` (fullmatch). This is the full Git commit SHA of the source the measurement was taken against, used to tie the receipt to a reproducible provenance. Uppercase hex, abbreviated SHAs (7-12 chars), prefixes like `sha:`, or non-string values are rejected.
Solutions
- Use the full SHA: `git rev-parse HEAD` (not --short) and store the 40-char lowercase result
- Normalize the value: `receipt["source_sha"] = receipt["source_sha"].strip().lower()` before validation
- Regenerate the receipt with the measurement script, which fills source_sha automatically via `git rev-parse HEAD`
Example fix
// before "source_sha": "9d4A1c" // short/uppercase // after "source_sha": "9d4a1c0f..." // full 40-char lowercase from `git rev-parse HEAD`
Defensive patterns
Strategy: validation
Validate before calling
import re
SHA = re.compile(r"[0-9a-f]{40}")
assert isinstance(receipt.get("source_sha"), str) and SHA.fullmatch(receipt["source_sha"]) Type guard
def is_full_git_sha(v):
return isinstance(v, str) and re.fullmatch(r"[0-9a-f]{40}", v) is not None Try / catch
try:
validate_receipt(receipt)
except PersistenceBacklogError as e:
if "source_sha must be an exact lowercase Git SHA" in str(e):
receipt["source_sha"] = subprocess.check_output(["git","rev-parse","HEAD"], text=True).strip() Prevention
- Always use `git rev-parse HEAD` (no --short) when recording source SHAs
- Lowercase and strip SHAs before storing them
- Reject abbreviated SHAs at receipt-generation time
When it happens
Trigger: Receipt stores a short SHA like `abc1234`; SHA written in uppercase; SHA stored as a number or with a `refs/heads/` prefix; a hand-written receipt with a placeholder value.
Common situations: Copying `git rev-parse --short HEAD` output into a hand-authored receipt; pasting a GitHub UI SHA that was displayed abbreviated; a script storing `git log -1 --format=%H` output with trailing newline preserved incorrectly (via fullmatch that also fails on whitespace).
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- receipt does not match the checked source
- receipt must be a version string
- baseline provenance must identify a clean source tree
- baseline provenance needs an exact source SHA
- CRON field contains an empty list item
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/1c3201be68b0c1ba.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/check-persistence-backlog-budget.py:156
*,
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",
"cargo_version",
"build_profile",
"sample_count",
):
if receipt[field] != expected_source[field]:
raise PersistenceBacklogError(View on GitHub (pinned to 433685b202)