JuliusBrussee/caveman · error · Error

binary download failed: HTTP

Error message

binary download failed: HTTP ${response.status}

What it means

The binary-installer fetches a release artifact from a remote URL via `asset()`, which performs an HTTP GET with an AbortSignal timeout. When the server responds with a non-2xx status (`response.ok` is false), the installer refuses to continue and throws this error carrying the numeric HTTP status. It exists so a failed download surfaces the server's status code instead of a generic fetch failure.

Solutions

  1. Check the HTTP status in the message and curl the exact release URL to confirm what the server returns.
  2. If 404: verify the release tag/version the installer is pointed at still exists and the artifact name is correct.
  3. If 403/429: wait out the rate limit or provide authentication (GITHUB_TOKEN / proxy credentials) for the download.
  4. If 5xx: retry after a short delay; the failure is server-side.
  5. As a workaround, install the binary manually and point the envVar (e.g. CAVEMAN_*_BIN) at it so `ensureBinary` short-circuits before any download.

Example fix

// before (fails with HTTP status)
await ensureBinary({ name: "ripgrep", envVar: "CAVEMAN_RIPGREP_BIN" });
// after: pin a verified binary or pre-seed the env var
if (!process.env.CAVEMAN_RIPGREP_BIN) process.env.CAVEMAN_RIPGREP_BIN = "/usr/local/bin/rg";
await ensureBinary({ name: "ripgrep", envVar: "CAVEMAN_RIPGREP_BIN" });
Defensive patterns

Strategy: try-catch

Validate before calling

// optional pre-flight reachability check
const res = await fetch(releaseUrl, { method: "HEAD" });
if (!res.ok) throw new Error(`release unavailable: HTTP ${res.status}`);

Try / catch

try {
  await ensureBinary({ name, envVar });
} catch (e) {
  if (String(e.message).includes("HTTP ")) {
    const status = Number(e.message.match(/HTTP (\d+)/)?.[1]);
    if (status === 429 || status === 403) await backoffAndRetry();
    else throw e;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `ensureBinary({ name, envVar })` (directly or through the download flow) when `asset(url, timeout)` receives an HTTP response with status >= 400 — e.g. the release URL 404s because the version/tag doesn't exist, a proxy returns 403/407, or the CDN rate-limits with 429. Also reached from `download()` via its internal `asset()` call.

Common situations: Wrong or stale release base URL configured (CAVEMAN release feed moved or tag deleted); corporate proxy blocking github releases (403); GitHub rate limiting unauthenticated downloads (429/403); transient 5xx from the CDN; offline VPN environment returning an error page status.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

}

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"),
    );
  } catch {

View on GitHub (pinned to 3ee70a1026)