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
- Inspect the actual archive contents: tar -tzf <archive> (or unzip -l) and compare basenames against the member name in the error.
- 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').
- Upgrade headroom-ai — registry fixes for layout changes usually land quickly after upstream releases.
- 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 bumping tool versions in the registry, always verify the archive listing matches the member field (case-insensitive basename).
- Remember matching is by basename and case-insensitive — leading directories are fine.
- Add a CI job that downloads each registry tool on each supported platform and runs the extraction end-to-end.
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
- failed to extract {archive.name}: {e}
- {tool}: distributed via PyPI only; `pip install headroom-ai`
- unknown tool {tool!r}; known: {sorted(tools)}
- {tool}: no prebuilt binary for {plat.key()}; supported: {sup
- offline mode (HEADROOM_BINARIES_OFFLINE=1) but fetch require
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/d789b60041c21be4.
Report an issue: GitHub.