JuliusBrussee/caveman · error

binary download failed: ${error.message}

Error message

binary download failed: ${error.message}

What it means

asset() in the binary installer wraps fetch() with AbortSignal.timeout; any thrown error (DNS failure, connection refused, TLS problem, or the abort timeout firing) is rethrown as 'binary download failed: <cause>'. The message preserves the underlying error text for diagnosis.

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 27d5a3981a)

Solutions

  1. If the message mentions abort or timeout, raise CAVE_SETUP_TIMEOUT (for example 600) and retry
  2. Verify network reachability of the release base URL from that machine (curl the same URL)
  3. For proxies, configure one that global fetch honors (undici EnvHttpProxyAgent or NODE_USE_ENV_PROXY=1), or run the install where egress is open
  4. Pre-install the binary on a connected machine and point CAVEMAN_MCP_BIN / CAVEMAN_SHRINK_BIN / CAVEMAN_BROWSE_BIN at it

Example fix

# before
CAVE_SETUP_TIMEOUT=30 npm run setup   # 'binary download failed: TimeoutError: The operation was aborted'

# after
CAVE_SETUP_TIMEOUT=600 npm run setup
Defensive patterns

Strategy: retry

Validate before calling

// probe reachability before setup
await fetch(releaseBase, { signal: AbortSignal.timeout(5000) })
  .catch(() => { throw new Error("release host unreachable — check egress/proxy"); });

Try / catch

for (const wait of [0, 2000, 8000]) {
  await sleep(wait);
  try { return await ensureBinary({ name, envVar }); }
  catch (e) {
    if (!/^binary download failed: /.test(String(e?.message)) || /HTTP \d+/.test(e.message)) throw e;
  }
}
// retry transport failures only; HTTP-status failures are not retried blindly

Prevention

When it happens

Trigger: Offline machine, blocked DNS, firewall or egress rules denying the release host, a corporate proxy not honored by undici fetch, or a slow link exceeding CAVE_SETUP_TIMEOUT (abort/TimeoutError appears in the message).

Common situations: CI runners without internet egress; air-gapped environments; HTTPS_PROXY set but undici ignoring it; slow links timing out at the default 300 seconds.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/aecd62c308c613e4. Report an issue: GitHub.