JuliusBrussee/caveman · error · Error

binary download failed

Error message

binary download failed: ${error.message}

What it means

`asset` wraps the installer's fetch of a binary/checksum URL. Any network- or abort-level failure of fetch (DNS failure, connection refused/reset, TLS error, or the AbortSignal.timeout firing) is rethrown as `binary download failed: <original message>`, preserving the cause's message.

Solutions

  1. Check basic egress first: curl -I the URL from the same machine to see the underlying cause
  2. Configure HTTPS_PROXY/HTTP_PROXY if the environment requires a proxy for outbound fetch
  3. Raise CAVE_SETUP_TIMEOUT so AbortSignal.timeout doesn't abort slow downloads
  4. Retry the install (transient network/DNS failures) or pre-download the binary and install it manually

Example fix

// before
// installer aborts on slow link: binary download failed: The operation was aborted due to timeout
// after
CAVE_SETUP_TIMEOUT=900 npx installer
Defensive patterns

Strategy: retry

Validate before calling

// probe egress before install
const probe = await fetch("https://github.com", { method: "HEAD", signal: AbortSignal.timeout(5000) }).catch(() => null);
if (!probe) throw new Error("no outbound network access; configure proxy or vendor the binary");

Try / catch

async function withRetry(fn, n = 3) {
  let last;
  for (let i = 0; i < n; i++) {
    try { return await fn(); }
    catch (e) {
      if (!String(e.message).startsWith("binary download failed:")) throw e;
      last = e;
      await new Promise(r => setTimeout(r, 2 ** i * 1000));
    }
  }
  throw last;
}

Prevention

When it happens

Trigger: fetch rejects: no network/DNS, unreachable or firewalled host, proxy issues, TLS interception, or the request exceeding the timeout passed to asset (AbortSignal.timeout aborts).

Common situations: Corporate proxy blocking the download host; GitHub Releases rate-limit or outage; air-gapped CI with no egress; slow link where the setup timeout (CAVE_SETUP_TIMEOUT) expires mid-download.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/aecd62c308c613e4. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/binary-installer/installer.mjs:92

export function binaryInstallFilename(name, os = process.platform) {
  return os === "win32" ? `${name}.exe` : name;
}

function timeoutMs() {
  const raw = process.env.CAVE_SETUP_TIMEOUT ?? "300";
  const seconds = Number(raw);
  if (!Number.isInteger(seconds) || seconds <= 0) {
    throw new Error(`CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.stringify(raw)})`);
  }
  return seconds * 1000;
}

async function asset(url, timeout) {
  let response;
  try {
    response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
  } catch (error) {
    throw new Error(`binary download failed: ${error.message}`);
  }
  if (!response.ok) throw new Error(`binary download failed: HTTP ${response.status}`);
  return response;
}

function signedDigest(checksums, bundleRaw) {
  try {
    const bundle = JSON.parse(bundleRaw);
    if (bundle.mediaType !== "application/vnd.dev.sigstore.bundle.v0.3+json") return false;
    if (bundle.messageSignature?.messageDigest?.algorithm !== "SHA2_256") return false;
    const digest = createHash("sha256").update(checksums).digest();
    const bundled = Buffer.from(bundle.messageSignature.messageDigest.digest, "base64");
    if (digest.length !== bundled.length || !digest.equals(bundled)) return false;
    return verify(
      "sha256",
      Buffer.from(checksums),
      createPublicKey(BINARY_SIGNING_PUBKEY),
      Buffer.from(bundle.messageSignature.signature, "base64"),

View on GitHub (pinned to 3ee70a1026)