headroomlabs-ai/headroom · error · Sha256Mismatch

sha256 mismatch for {path.name}: expected {expected}, got {g

Error message

sha256 mismatch for {path.name}: expected {expected}, got {got}

What it means

After download, _verify_sha256 hashes the file and compares (case-insensitively) against the registry-pinned sha256. On mismatch the downloaded file is deleted immediately (so a bad artifact never lingers in cache) and Sha256Mismatch is raised with both the expected and actual digests. When the registry has no pin, the function only logs at INFO and trusts HTTPS — this error therefore always means a pin WAS present and did not match.

Source

Thrown at headroom/binaries.py:319

def _sha256_file(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1024 * 64), b""):
            h.update(chunk)
    return h.hexdigest()


def _verify_sha256(path: Path, expected: str | None) -> None:
    if not expected:
        # Upstream release not SHA-pinned in registry. HTTPS + the GitHub CDN
        # is the only integrity check. Log at INFO so verbose runs can see
        # this state; `doctor` surfaces the same fact via `sha_pinned=False`.
        logger.info("binary %s downloaded without sha256 pin (HTTPS trust only)", path.name)
        return
    got = _sha256_file(path)
    if got.lower() != expected.lower():
        path.unlink(missing_ok=True)
        raise Sha256Mismatch(f"sha256 mismatch for {path.name}: expected {expected}, got {got}")


# ---------- Archive extraction ------------------------------------------- #


def _extract(archive: Path, member: str, dest: Path) -> None:
    """Extract `member` from archive into `dest` (single-file binary)."""
    if not _has_writable_existing_parent(dest.parent):
        raise OSError(f"binary cache directory parent is not writable: {dest.parent}")
    dest.parent.mkdir(parents=True, exist_ok=True)
    if not _is_writable_dir(dest.parent):
        raise OSError(f"binary cache directory is not writable: {dest.parent}")
    name = archive.name.lower()
    try:
        if name.endswith(".tar.gz") or name.endswith(".tgz"):
            with tarfile.open(archive, "r:gz") as tf:
                _extract_member_from_tar(tf, member, dest)
        elif name.endswith(".zip"):

View on GitHub (pinned to 322425c43b)

Solutions

  1. Update headroom-ai so its registry carries the sha256 of the current upstream release.
  2. If using HEADROOM_BINARIES_MIRROR, verify the mirror serves byte-identical assets (compare sha256sum against GitHub's published digest).
  3. Independently confirm which side is wrong: download from github.com directly and hash it; if the direct hash matches 'got', the registry pin is stale (file an issue); if it matches 'expected', the mirror is tampering/corrupting.
  4. Treat unexpected mismatches on a trusted mirror as a security incident, not an inconvenience.

Example fix

# before
HEADROOM_BINARIES_MIRROR=https://mirror.internal/gh
# -> sha256 mismatch: expected a1b2..., got c3d4...

# after
# verify mirror integrity, or bypass it for this fetch:
HEADROOM_BINARIES_MIRROR= headroom doctor  # re-download from github.com
Defensive patterns

Strategy: try-catch

Validate before calling

import hashlib

def artifact_matches_pin(url: str, expected_sha: str | None) -> bool:
    if not expected_sha:
        return True  # unpinned: HTTPS-only trust
    data = urllib.request.urlopen(url, timeout=60).read()
    return hashlib.sha256(data).hexdigest() == expected_sha.lower()

# pre-check mirror integrity before enabling it fleet-wide

Try / catch

from headroom.binaries import Sha256Mismatch

try:
    ensure_binary(tool)
except Sha256Mismatch as e:
    # do NOT retry blindly: decide which side is wrong
    logger.critical("integrity failure: %s", e)
    raise SystemExit("mirror or registry is serving/pinning wrong bytes; escalate") from e

Prevention

When it happens

Trigger: The bytes served for the pinned URL differ from the registry's digest: upstream release re-tagged/overwritten without a registry update, a mirror serving a different (possibly malicious or truncated) artifact, or a corrupted download.

Common situations: Internal mirrors that re-pack assets, GitHub releases where a maintainer force-pushed a tag, stale headroom-ai registry after a tool upstream re-released, or genuine CDN corruption.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/43a7092b7b4ca4ea. Report an issue: GitHub.