{"record":{"id":"4b4ae45739663024","repo":"JuliusBrussee/caveman","slug":"binary-download-failed-http-response-status","errorCode":null,"errorMessage":"binary download failed: HTTP ${response.status}","messagePattern":"binary download failed: HTTP (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/binary-installer/installer.mjs","lineNumber":94,"sourceCode":"}\n\nfunction timeoutMs() {\n  const raw = process.env.CAVE_SETUP_TIMEOUT ?? \"300\";\n  const seconds = Number(raw);\n  if (!Number.isInteger(seconds) || seconds <= 0) {\n    throw new Error(`CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.stringify(raw)})`);\n  }\n  return seconds * 1000;\n}\n\nasync function asset(url, timeout) {\n  let response;\n  try {\n    response = await fetch(url, { signal: AbortSignal.timeout(timeout) });\n  } catch (error) {\n    throw new Error(`binary download failed: ${error.message}`);\n  }\n  if (!response.ok) throw new Error(`binary download failed: HTTP ${response.status}`);\n  return response;\n}\n\nfunction signedDigest(checksums, bundleRaw) {\n  try {\n    const bundle = JSON.parse(bundleRaw);\n    if (bundle.mediaType !== \"application/vnd.dev.sigstore.bundle.v0.3+json\") return false;\n    if (bundle.messageSignature?.messageDigest?.algorithm !== \"SHA2_256\") return false;\n    const digest = createHash(\"sha256\").update(checksums).digest();\n    const bundled = Buffer.from(bundle.messageSignature.messageDigest.digest, \"base64\");\n    if (digest.length !== bundled.length || !digest.equals(bundled)) return false;\n    return verify(\n      \"sha256\",\n      Buffer.from(checksums),\n      createPublicKey(BINARY_SIGNING_PUBKEY),\n      Buffer.from(bundle.messageSignature.signature, \"base64\"),\n    );\n  } catch {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/binary-installer/installer.mjs#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the HTTP status in the message and curl the exact release URL to confirm what the server returns.","If 404: verify the release tag/version the installer is pointed at still exists and the artifact name is correct.","If 403/429: wait out the rate limit or provide authentication (GITHUB_TOKEN / proxy credentials) for the download.","If 5xx: retry after a short delay; the failure is server-side.","As a workaround, install the binary manually and point the envVar (e.g. CAVEMAN_*_BIN) at it so `ensureBinary` short-circuits before any download."],"exampleFix":"// before (fails with HTTP status)\nawait ensureBinary({ name: \"ripgrep\", envVar: \"CAVEMAN_RIPGREP_BIN\" });\n// after: pin a verified binary or pre-seed the env var\nif (!process.env.CAVEMAN_RIPGREP_BIN) process.env.CAVEMAN_RIPGREP_BIN = \"/usr/local/bin/rg\";\nawait ensureBinary({ name: \"ripgrep\", envVar: \"CAVEMAN_RIPGREP_BIN\" });","handlingStrategy":"try-catch","validationCode":"// optional pre-flight reachability check\nconst res = await fetch(releaseUrl, { method: \"HEAD\" });\nif (!res.ok) throw new Error(`release unavailable: HTTP ${res.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  await ensureBinary({ name, envVar });\n} catch (e) {\n  if (String(e.message).includes(\"HTTP \")) {\n    const status = Number(e.message.match(/HTTP (\\d+)/)?.[1]);\n    if (status === 429 || status === 403) await backoffAndRetry();\n    else throw e;\n  } else throw e;\n}","preventionTips":["Pin a known-good release version instead of following a moving latest tag.","Use authenticated requests where possible to avoid GitHub rate limits.","Pre-seed the envVar with a locally installed binary in CI/offline environments.","Monitor the release feed for deleted/renamed tags."],"tags":["network","download","http"],"backgroundTag":"http-error-response","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}