{"record":{"id":"301bab80770d0d68","repo":"oven-sh/bun","slug":"range-url-res-status","errorCode":null,"errorMessage":"Range ${url}: ${res.status}","messagePattern":"Range (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/binary-size.ts","lineNumber":328,"sourceCode":"  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}`);\n\n  let size = 0;\n  while (p + 46 <= eocd && dv.getUint32(p, true) === 0x02014b50) {\n    const uncompressed = dv.getUint32(p + 24, true);","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/oven-sh/bun/blob/8c5296ac459e8252d3cd702f3fbcbb0c249d95d5/scripts/binary-size.ts#L310-L346","documentation":"After the HEAD probe, zipBinarySize() requests the last ~64 KB of the zip via a Range header (bytes=total-tail through total-1) to read the central directory. This error means that Range request returned non-2xx — the server refused or failed the partial download.","triggerScenarios":"A CDN or proxy that ignores or rejects Range on signed URLs (416 when byte math is off, 403 when the second signed request no longer validates), or the object changed between HEAD and Range so length/signature no longer match.","commonSituations":"GitHub asset CDN expiring the signed URL between the two requests; corporate proxies stripping Range; a missing/zero content-length on the HEAD making the Range header malformed.","solutions":["Re-run — signed-URL expiry between HEAD and Range usually resolves itself","Ensure the HEAD actually returned a valid content-length before the Range is built","As a manual fallback, download the whole zip and read the binary size locally"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"const total = Number(head.headers.get(\"content-length\"));\nif (!Number.isFinite(total) || total <= 0) {\n  // Range math would be garbage; fall back to a full download\n  return sizeFromFullZip(new Uint8Array(await (await fetch(url)).arrayBuffer()));\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await zipBinarySize(url);\n} catch (e) {\n  if (/^Range /.test(e.message)) {\n    const buf = new Uint8Array(await (await fetch(url)).arrayBuffer()); // full-download fallback\n    return sizeFromFullZip(buf);\n  }\n  throw e;\n}","preventionTips":["Issue HEAD and Range back-to-back to shrink the signed-URL expiry window","Validate content-length before building the Range header"],"tags":["http","network","range-request","zip","binary-size"],"backgroundTag":null,"analyzedSha":"8c5296ac459e8252d3cd702f3fbcbb0c249d95d5","analyzedAt":"2026-08-16T08:01:58.794Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}