{"record":{"id":"58547daa00a2c995","repo":"oven-sh/bun","slug":"head-url-head-status","errorCode":null,"errorMessage":"HEAD ${url}: ${head.status}","messagePattern":"HEAD (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/binary-size.ts","lineNumber":324,"sourceCode":"}\n\nasync function sizesFromZips(triplets: string[], urls: Map<string, string>): Promise<Sizes> {\n  const out: Sizes = {};\n  await Promise.all(\n    triplets.map(async t => {\n      out[t] = await zipBinarySize(urls.get(t)!);\n    }),\n  );\n  return out;\n}\n\n// Read the uncompressed size of the binary inside a release zip without\n// downloading the whole archive. The central directory + EOCD live at the end\n// of the file; a 64 KB Range request is more than enough for our two-entry\n// (`<triplet>/` + `<triplet>/bun[.exe]`) zips.\nasync function zipBinarySize(url: string): Promise<number> {\n  const head = await fetch(url, { method: \"HEAD\" });\n  if (!head.ok) throw new Error(`HEAD ${url}: ${head.status}`);\n  const total = Number(head.headers.get(\"content-length\"));\n  const tail = Math.min(65536, total);\n  const res = await fetch(url, { headers: { Range: `bytes=${total - tail}-${total - 1}` } });\n  if (!res.ok) throw new Error(`Range ${url}: ${res.status}`);\n  const buf = new Uint8Array(await res.arrayBuffer());\n  const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);\n\n  let eocd = -1;\n  for (let i = buf.length - 22; i >= Math.max(0, buf.length - 22 - 65535); i--) {\n    if (dv.getUint32(i, true) === 0x06054b50) {\n      eocd = i;\n      break;\n    }\n  }\n  if (eocd < 0) throw new Error(`no zip EOCD in ${url}`);\n\n  let p = dv.getUint32(eocd + 16, true) - (total - tail);\n  if (p < 0) throw new Error(`zip central directory not within tail for ${url}`);","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/scripts/binary-size.ts#L306-L342","documentation":"zipBinarySize() in scripts/binary-size.ts issues a HEAD request against a release asset URL to learn content-length (needed to compute the tail Range window). This error means the HEAD returned non-2xx, so the size probe cannot proceed.","triggerScenarios":"A stale browser_download_url (they can expire or rotate when an asset is re-published), a CDN that rejects HEAD, or 404 because the release asset was renamed or removed since the listing was fetched.","commonSituations":"Comparing against a canary release that was re-published mid-run; asset naming changes (new triplet/musl/baseline variants) no longer matching isBinaryZip; transient CDN 5xx.","solutions":["Re-run — transient CDN errors usually clear","Re-fetch the release asset lists so browser_download_url values are fresh instead of reusing cached ones","Verify the asset still exists: curl -I the URL","Check that the isBinaryZip pattern still matches current asset naming"],"exampleFix":"# before — cached release listing yields a dead URL\nError: HEAD https://objects.githubusercontent.com/...: 403\n\n# after — resolve fresh asset URLs every run\nconst [latest, canary] = await Promise.all([gh(\"releases/latest\"), gh(\"releases/tags/canary\")]);","handlingStrategy":"retry","validationCode":"const probe = await fetch(url, { method: \"HEAD\" });\nif (!probe.ok || !Number(probe.headers.get(\"content-length\"))) {\n  throw new Error(`asset unreachable or size unknown: ${url}`);\n}","typeGuard":null,"tryCatchPattern":"try { size = await zipBinarySize(url); }\ncatch (e) {\n  if (/^HEAD /.test(e.message)) { urls = await refreshAssetUrls(); continue; } // re-resolve and retry\n  throw e;\n}","preventionTips":["Never cache browser_download_url across long-lived sessions","HEAD before Range so a dead URL fails fast with a clear signal"],"tags":["http","network","zip","binary-size","cdn"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}