Stirling-Tools/Stirling-PDF · error · SystemExit

{member} not found in {asset}

Error message

{member} not found in {asset}

What it means

install_gitleaks.py downloads a pinned gitleaks release archive, verifies its SHA-256, then extracts the single binary named `gitleaks` (or `gitleaks.exe`) from it via tarfile/zipfile. This SystemExit fires when tarfile.extractfile(member) returns None on line 97 -- i.e. the verified archive contains no top-level entry with that exact name. Because the checksum already passed, the archive is intact and the correct version; the mismatch is purely the expected path inside the tarball. It almost always means gitleaks changed how it packages its release assets (e.g. nesting the binary in a directory) after a VERSION bump in this script.

Source

Thrown at scripts/pre-commit/install_gitleaks.py:97

    asset = f"gitleaks_{VERSION}_{key}.{suffix}"
    url = f"https://github.com/gitleaks/gitleaks/releases/download/v{VERSION}/{asset}"
    print(f"Downloading gitleaks {VERSION} ({asset})", flush=True)

    BIN.parent.mkdir(parents=True, exist_ok=True)
    archive, _ = urllib.request.urlretrieve(url)
    digest = hashlib.sha256(Path(archive).read_bytes()).hexdigest()
    if digest != expected:
        raise SystemExit(f"gitleaks checksum mismatch: expected {expected}, got {digest}")

    member = "gitleaks.exe" if IS_WINDOWS else "gitleaks"
    if suffix == "zip":
        with zipfile.ZipFile(archive) as zf:
            data = zf.read(member)
    else:
        with tarfile.open(archive) as tf:
            extracted = tf.extractfile(member)
            if extracted is None:
                raise SystemExit(f"{member} not found in {asset}")
            data = extracted.read()
    BIN.write_bytes(data)
    BIN.chmod(0o755)
    return 0


if __name__ == "__main__":
    sys.exit(main())

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Inspect the archive contents (`tar -tzf gitleaks_<ver>_<key>.tar.gz`) to find the actual binary path and update the `member` assignment at install_gitleaks.py:89, or resolve it dynamically from tf.getnames()
  2. Confirm the VERSION and the matching SHA256[key] both come from the same release whose tarball layout you verified locally
  3. If upstream packaging is unstable across releases, resolve the member by basename: `member = next(m for m in tf.getnames() if Path(m).name == wanted)` so directory-prefixed entries are tolerated

Example fix

# before (install_gitleaks.py:94-98)
member = "gitleaks.exe" if IS_WINDOWS else "gitleaks"
with tarfile.open(archive) as tf:
    extracted = tf.extractfile(member)
    if extracted is None:
        raise SystemExit(f"{member} not found in {asset}")
    data = extracted.read()

# after
wanted = "gitleaks.exe" if IS_WINDOWS else "gitleaks"
with tarfile.open(archive) as tf:
    candidates = [m for m in tf.getnames() if Path(m).name == wanted]
    if not candidates:
        raise SystemExit(f"{wanted} not found in {asset}; members: {tf.getnames()}")
    extracted = tf.extractfile(candidates[0])
    data = extracted.read()
Defensive patterns

Strategy: validation

Validate before calling

import tarfile, zipfile
from pathlib import Path

def archive_has_member(archive: str, wanted: str) -> bool:
    if archive.endswith(".zip"):
        with zipfile.ZipFile(archive) as zf:
            return any(Path(n).name == wanted for n in zf.namelist())
    with tarfile.open(archive) as tf:
        return any(Path(m).name == wanted for m in tf.getnames())

Try / catch

try:
    extracted = tf.extractfile(member)
    if extracted is None:
        # fall back to basename discovery rather than aborting
        member = next(m for m in tf.getnames() if Path(m).name == wanted)
        extracted = tf.extractfile(member)
    data = extracted.read()
except (StopIteration, KeyError) as exc:
    raise SystemExit(f"{wanted} not found in {asset}") from exc

Prevention

When it happens

Trigger: Bumping VERSION (install_gitleaks.py:26) without confirming the new release's internal tarball layout; gitleaks upstream starts nesting the binary under a top-level directory; running on a platform whose asset name resolves to a differently structured archive. Note the checksum on line 86 already validated the bytes, so this is a layout problem, not corruption.

Common situations: A maintainer updates the gitleaks pin to a new minor release and upstream packaging changed in the same release; CI runs `task pre-commit` for the first time on a newly added platform; the windows zip uses a different internal name than `gitleaks.exe`.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/725c63224e9c80f5. Report an issue: GitHub.