headroomlabs-ai/headroom · error · BinaryFetchError

archive did not contain expected member {member!r}

Error message

archive did not contain expected member {member!r}

What it means

After scanning every member of a tar (_extract_member_from_tar) or zip (_extract_member_from_zip), the expected binary was not found, so BinaryFetchError is raised. Matching is by basename, case-insensitive (e.g. registry name 'difft' matches 'difft-0.64.0/difft' inside the tar), and only regular files count. The error names the exact member string that failed to match.

Source

Thrown at headroom/binaries.py:366

            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}")


def _extract_member_from_zip(zf: zipfile.ZipFile, member: str, dest: Path) -> None:
    wanted = member.lower()
    for info in zf.infolist():
        base = info.filename.rsplit("/", 1)[-1].lower()
        if base == wanted and not info.is_dir():
            with zf.open(info) as src, dest.open("wb") as out:
                shutil.copyfileobj(src, out)
            return
    raise BinaryFetchError(f"archive did not contain expected member {member!r}")


# ---------- Public API ---------------------------------------------------- #


def _binary_name(tool: str, plat: PlatformKey) -> str:
    entry = _tool_entry(tool)

View on GitHub (pinned to 322425c43b)

Solutions

  1. Inspect the actual archive contents: tar -tzf <archive> (or unzip -l) and compare basenames against the member name in the error.
  2. Update the registry entry's member field to the real basename (case-insensitive matching means only the basename must be right, e.g. 'difft' or 'difft.exe').
  3. Upgrade headroom-ai — registry fixes for layout changes usually land quickly after upstream releases.
  4. If you control the tool's release process, keep a stable top-level binary name inside archives.

Example fix

# before (registry.json)
{"tools": {"difft": {"member": "difftastic"}}}
# error: archive did not contain expected member 'difftastic'

# after
tar -tzf difft-*.tar.gz  # shows difft-0.64.0/difft
{"tools": {"difft": {"member": "difft"}}}  # basename match works
Defensive patterns

Strategy: validation

Validate before calling

import tarfile, zipfile

def archive_contains_member(archive: str, member: str) -> bool:
    wanted = member.lower()
    name = archive.lower()
    if name.endswith((".tar.gz", ".tgz")):
        with tarfile.open(archive) as tf:
            return any(m.name.rsplit("/", 1)[-1].lower() == wanted and m.isfile() for m in tf.getmembers())
    if name.endswith(".zip"):
        with zipfile.ZipFile(archive) as zf:
            return any(i.filename.rsplit("/", 1)[-1].lower() == wanted and not i.is_dir() for i in zf.infolist())
    return True  # plain/gz single-binary archives copy the file directly

# check before deploying a registry change
assert archive_contains_member(downloaded, "difft")

Try / catch

from headroom.binaries import BinaryFetchError

try:
    ensure_binary(tool)
except BinaryFetchError as e:
    if "did not contain expected member" in str(e):
        raise SystemExit(f"registry member name for {tool} is stale; update it after checking the archive listing") from e
    raise

Prevention

When it happens

Trigger: The registry's member name for a tool disagrees with what upstream actually shipped: upstream renamed the binary inside the archive (e.g. added a version or .exe suffix pattern), restructured leading directories, or shipped a top-level directory where the registry expected a file.

Common situations: A tool upstream changed its release archive layout after the headroom-ai registry entry was written; Windows assets where the member is 'tool.exe' but the registry says 'tool'; a new tool added to the registry with a guessed member name.

Related errors


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