headroomlabs-ai/headroom · error · BinaryFetchError

failed to extract {archive.name}: {e}

Error message

failed to extract {archive.name}: {e}

What it means

_extract wraps all unpacking (tarfile, zipfile, gzip, plain copy) and converts tarfile.TarError, zipfile.BadZipFile, and OSError into BinaryFetchError chained with 'from e'. It means the artifact was fetched and its sha256 (if pinned) verified, but the bytes still could not be unpacked — truncated archive, corrupt compressed stream, or an I/O failure mid-copy.

Source

Thrown at headroom/binaries.py:350

    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"):
            with zipfile.ZipFile(archive) as zf:
                _extract_member_from_zip(zf, member, dest)
        elif name.endswith(".gz") and not (name.endswith(".tar.gz") or name.endswith(".tgz")):
            # bare .gz of a single binary (e.g. `scc-linux-x86_64.gz`)
            import gzip

            with gzip.open(archive, "rb") as gz, dest.open("wb") as out:
                shutil.copyfileobj(gz, out)
        else:
            # Not an archive — treat the downloaded file itself as the binary.
            shutil.copy2(archive, dest)
    except (tarfile.TarError, zipfile.BadZipFile, OSError) as e:
        raise BinaryFetchError(f"failed to extract {archive.name}: {e}") from e


def _extract_member_from_tar(tf: tarfile.TarFile, member: str, dest: Path) -> None:
    # Match by basename so that registries can specify "difft" even though the
    # upstream tar may include a leading directory like "difft-0.64.0/difft".
    wanted = member.lower()
    for m in tf.getmembers():
        base = m.name.rsplit("/", 1)[-1].lower()
        if base == wanted and m.isfile():
            extracted = tf.extractfile(m)
            if extracted is None:
                continue
            with dest.open("wb") as out:
                shutil.copyfileobj(extracted, out)
            return
    raise BinaryFetchError(f"archive did not contain expected member {member!r}")

View on GitHub (pinned to 322425c43b)

Solutions

  1. Identify the artifact: file <archive> — 'HTML document' means the mirror/proxy served an error page; fix the mirror or bypass it.
  2. Clear the corrupted cache entry and re-download from a clean source (the mismatching artifact was cached before extraction failed only if sha was unpinned).
  3. Check disk space on the cache volume: df -h <cache-dir>; ENOSPC surfaces as OSError here.
  4. Prefer sha256-pinned registry entries so corrupt downloads are caught before extraction is attempted.

Example fix

# before
# BinaryFetchError: failed to extract scc-linux-x86_64.gz: Not a gzipped file

# after: the 'archive' was a proxy error page; bypass the mirror
HEADROOM_BINARIES_MIRROR= headroom doctor
Defensive patterns

Strategy: try-catch

Validate before calling

import gzip, tarfile, zipfile

def archive_intact(path: str) -> bool:
    name = path.lower()
    try:
        if name.endswith((".tar.gz", ".tgz")):
            tarfile.open(path).getmembers()
        elif name.endswith(".zip"):
            zipfile.ZipFile(path).testzip()
        elif name.endswith(".gz"):
            with gzip.open(path, "rb") as f:
                f.read()
        return True
    except (tarfile.TarError, zipfile.BadZipFile, OSError):
        return False

Try / catch

from headroom.binaries import BinaryFetchError

try:
    ensure_binary(tool)
except BinaryFetchError as e:
    if "failed to extract" in str(e):
        purge_cache_entry(tool)          # drop the corrupt artifact
        ensure_binary(tool)              # one fresh re-download
    else:
        raise

Prevention

When it happens

Trigger: A gzip/tar/zip archive that is structurally invalid despite passing an (absent) sha256 pin: 'not a gzip file', 'unexpected end of data', EOF truncation, or disk-full (ENOSPC) while writing the extracted binary.

Common situations: Unpinned upstream artifacts that were later corrupted, a mirror serving error pages (HTML) for asset URLs, partial writes from an interrupted earlier download, or a full disk / quota on the cache volume.

Related errors


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