{"record":{"id":"0a058dd5db8bb2e0","repo":"can1357/oh-my-pi","slug":"invalid-ar-archive-truncated-what","errorCode":null,"errorMessage":"Invalid ar archive: truncated ${what}","messagePattern":"Invalid ar archive: truncated (.+?)","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/unix-ar.ts","lineNumber":239,"sourceCode":"\t\t\tnameByteLength = decoded.byteLength;\n\t\t\tdataOffset += header.bsdNameLength;\n\t\t\tsize -= header.bsdNameLength;\n\t\t} else if (header.rawName === \"//\") {\n\t\t\tmetadataSize += header.physicalSize;\n\t\t\tassertIndexSize(metadataSize, options.limits, \"index\");\n\t\t\tlongNames = readMemoryRange(bytes, payloadOffset, payloadEnd);\n\t\t}\n\t\trecords.push({ name, nameByteLength, dataOffset, size, mtimeSeconds: header.mtimeSeconds, mode: header.mode });\n\t\tassertEntryCount(records.length, options.limits);\n\t\tposition = payloadEnd + (header.physicalSize & 1);\n\t\tif (position > bytes.byteLength) throw new ArchiveError(\"Invalid ar archive: missing alignment byte\");\n\t}\n\treturn materializeEntries(records, longNames, memoryByteSource(bytes), options);\n}\n\nasync function readExact(source: ByteSource, start: number, end: number, what: string): Promise<Uint8Array> {\n\tif (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > source.size) {\n\t\tthrow new ArchiveError(`Invalid ar archive: truncated ${what}`);\n\t}\n\ttry {\n\t\tconst bytes = await source.read(start, end);\n\t\tif (bytes.byteLength !== end - start) throw new ArchiveError(`Invalid ar archive: truncated ${what}`);\n\t\treturn bytes;\n\t} catch (error) {\n\t\tif (error instanceof ArchiveError) throw error;\n\t\tthrow new ArchiveError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nasync function readUnixArImpl(source: ByteSource, options: FormatReadOptions): Promise<ArchiveIndexEntry[]> {\n\tif (!Number.isSafeInteger(source.size) || source.size < SIGNATURE.length)\n\t\tthrow new ArchiveError(\"Invalid ar archive signature\");\n\treadSignatureFromBuffer(await readExact(source, 0, SIGNATURE.length, \"signature\"));\n\tconst records: RawArMember[] = [];\n\tlet longNames: Uint8Array | undefined;\n\tlet metadataSize = 0;","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/unix-ar.ts#L221-L257","documentation":"readExact validates the requested byte range before reading: start/end must be safe non-negative integers with end <= source.size and end >= start. Any violation means the range exceeds the archive's data — reported as a truncated region named by `what` ('signature', 'member header', long names, etc.).","triggerScenarios":"readUnixArImpl requests a header or payload range beyond source.size — e.g. the file is shorter than the 8-byte signature, or a header near EOF implies a range past the end; also headerBytes/nameBytes callers passing out-of-range ranges.","commonSituations":"Reading over an HTTP range where source.size was misreported (chunked/unknown length); truncated local files; a size race where the file shrinks between stat and read.","solutions":["Verify the byte source size matches the real file/stream length before parsing.","Re-download the archive and verify against publisher checksums.","If reading from a custom ByteSource, ensure `size` returns the true current size and `read(start,end)` errors or returns short only on genuine EOF.","Handle the ArchiveError and surface a 'file incomplete/corrupt' message to users."],"exampleFix":"// before\nconst entries = await readUnixAr(source);\n// after\nif (source.size < 8) throw new Error('file too small to be an ar archive');\nconst entries = await readUnixAr(source);","handlingStrategy":"validation","validationCode":"if (!Number.isSafeInteger(source.size) || source.size < 8) {\n  throw new Error(`byte source unusable: size=${source.size}`);\n}","typeGuard":"function isUsableSource(s: ByteSource): s is ByteSource & { size: number } {\n  return Number.isSafeInteger(s.size) && s.size >= 8;\n}","tryCatchPattern":"try {\n  return await readUnixAr(source);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated')) {\n    throw new Error(`cannot read ${what}: source shorter than declared`);\n  }\n  throw err;\n}","preventionTips":["Stat the file (or finalize the stream) before constructing the ByteSource so size is accurate.","For remote sources, verify Content-Length matches the served bytes.","Re-check size after any concurrent writes that could shrink the file.","Keep size and read() consistent in custom ByteSource implementations."],"tags":["archive","ar-format","truncated-file","io"],"backgroundTag":"truncated-archive-range-read","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}