{"record":{"id":"687dfed1d90b5466","repo":"can1357/oh-my-pi","slug":"remote-archive-range-request-failed-http-respon","errorCode":null,"errorMessage":"Remote archive range request failed (HTTP ${response.status})","messagePattern":"Remote archive range request failed \\(HTTP (.+?)\\)","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/source.ts","lineNumber":127,"sourceCode":"\t}\n\tawait probe.body?.cancel();\n\t// `Content-Range: bytes 0-0/12345` carries the total size.\n\tconst contentRange = probe.headers.get(\"content-range\");\n\tconst total = contentRange ? Number(/\\/(\\d+)$/.exec(contentRange)?.[1]) : Number.NaN;\n\tif (!Number.isSafeInteger(total) || total < 0) {\n\t\tthrow new ArchiveError(\"Remote archive did not report a valid size in Content-Range\");\n\t}\n\treturn {\n\t\tsize: total,\n\t\tasync read(start, end) {\n\t\t\tassertValidRange(start, end);\n\t\t\tif (start === end) return new Uint8Array(0);\n\t\t\tconst response = await doFetch(url, {\n\t\t\t\theaders: { ...options.headers, range: `bytes=${start}-${end - 1}` },\n\t\t\t});\n\t\t\tif (response.status !== 206) {\n\t\t\t\tawait response.body?.cancel();\n\t\t\t\tthrow new ArchiveError(`Remote archive range request failed (HTTP ${response.status})`);\n\t\t\t}\n\t\t\tconst bytes = new Uint8Array(await response.arrayBuffer());\n\t\t\tif (bytes.byteLength !== end - start) {\n\t\t\t\tthrow new ArchiveError(\"Invalid archive: truncated data\");\n\t\t\t}\n\t\t\treturn bytes;\n\t\t},\n\t};\n}\n\n/** Options for {@link cachingByteSource}. */\nexport interface CachingByteSourceOptions {\n\t/** Cache block size in bytes. Default 256 KiB. */\n\tblockSize?: number;\n\t/** Max cached blocks. Default 64 (16 MiB at the default block size). */\n\tmaxBlocks?: number;\n}\n","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/source.ts#L109-L145","documentation":"The ByteSource returned by httpByteSource() fulfills each read(start, end) with a ranged GET expecting a 206 Partial Content response. This throw fires when a later range read returns any other status — the server that supported the probe may have started rejecting requests, or per-request auth/URL problems surfaced mid-stream. The response body is cancelled before throwing.","triggerScenarios":"Any ByteSource.read(start, end) on the httpByteSource source where the ranged fetch returns non-206: presigned URL expired between probe and read (403), the object was deleted mid-session (404), rate limiting (429), transient 5xx, or a proxy that forwards the initial request but rejects subsequent ranged ones.","commonSituations":"Long-running extraction sessions over expiring S3 presigned URLs; load balancers routing reads to servers lacking the object; throttling under many concurrent small range reads; CDN misconfiguration that caches the probe but 4xx's range requests.","solutions":["Implement retry with fresh credentials/URL for each read — re-create httpByteSource with a newly signed URL when reads start failing","Add a retry wrapper around read() with backoff for 429/5xx statuses","Reduce concurrent range reads (or wrap with cachingByteSource) to avoid rate limiting","Fall back to downloading the whole archive to disk (fileByteSource) when the remote is unstable"],"exampleFix":"// before: single long-lived source dies when the presigned URL expires\nconst src = await httpByteSource(signedUrl);\nawait src.read(0, 512); // ... later 403\n// after: refresh the URL per read session\nasync function readRange(url, start, end) {\n\tconst src = await httpByteSource(await refreshSignedUrl(url));\n\treturn src.read(start, end);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function readResilient(src, start, end, attempts = 3) {\n\tfor (let i = 0; ; i++) {\n\t\ttry {\n\t\t\treturn await src.read(start, end);\n\t\t} catch (err) {\n\t\t\tif (i >= attempts || !(err instanceof ArchiveError) || !/HTTP \\d+/.test(err.message)) throw err;\n\t\t\tawait Bun.sleep(2 ** i * 250);\n\t\t}\n\t}\n}","preventionTips":["Use long-lived credentials or re-sign URLs per read for slow extractions","Wrap every remote read in bounded retry with backoff for 429/5xx","Wrap the source in cachingByteSource to reduce the number of HTTP range requests","Monitor extraction jobs and re-create httpByteSource when repeated non-206 statuses appear"],"tags":["http","network","remote-archive","range-requests"],"backgroundTag":"http-request-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}