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
- 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()
- Confirm the VERSION and the matching SHA256[key] both come from the same release whose tarball layout you verified locally
- 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 bumping VERSION, download the asset locally and run `tar -tzf` / unzip -l to confirm the binary's path before updating the pin
- Resolve the member by basename from tf.getnames() instead of hard-coding an exact path
- Treat any gitleaks minor bump as a packaging-change risk and verify on every platform in SHA256
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
- Unsupported platform for gitleaks: {platform.system()}/{plat
- No pinned gitleaks checksum for {key}
- gitleaks checksum mismatch: expected {expected}, got {digest
- {path}: invalid TOML: {exc}
- {path}: refusing to sort, the sorted output is not valid TOM
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/725c63224e9c80f5.
Report an issue: GitHub.