can1357/oh-my-pi · error · ArchiveError
Invalid archive: truncated data
Error message
Invalid archive: truncated data
What it means
`readMemoryRange` performs an exact in-memory read: if the requested end exceeds the buffer's byte length, the archive is truncated relative to what its own metadata claims, so it throws instead of silently clamping to a short slice. This protects parsers from operating on short reads that would produce garbage structures.
Source
Thrown at packages/utils/src/ar/source.ts:25
* only read when a member is actually extracted.
*/
export interface ByteSource {
readonly size: number;
read(start: number, end: number): Promise<Uint8Array>;
}
/** Reject a nonsensical `[start, end)` range before any read. */
export function assertValidRange(start: number, end: number): void {
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) {
throw new ArchiveError("Invalid archive range");
}
}
/** Read an exact in-memory range, throwing (not clamping) when it runs past the buffer. */
export function readMemoryRange(buffer: Uint8Array, start: number, end: number): Uint8Array {
assertValidRange(start, end);
if (end > buffer.byteLength) {
throw new ArchiveError("Invalid archive: truncated data");
}
return buffer.subarray(start, end);
}
/** Wrap borrowed bytes as a {@link ByteSource}. */
export function memoryByteSource(buffer: Uint8Array): ByteSource {
return {
size: buffer.byteLength,
async read(start, end) {
return readMemoryRange(buffer, start, end);
},
};
}
/** Lazily read ranges of a file on disk as a {@link ByteSource}. */
export function fileByteSource(filePath: string): ByteSource {
const file = Bun.file(filePath);
const size = file.size;View on GitHub (pinned to 9690622007)
Solutions
- Check the buffer length against the archive's declared total size before parsing
- Re-download or re-extract the file — truncation is the usual root cause
- Validate header-declared offsets+sizes against `source.size` before reading ranges
Example fix
// before: reading without checking declared size
const bytes = readMemoryRange(buf, offset, offset + size);
// after: bounds-check against the buffer first
if (offset + size > buf.byteLength) throw new Error("archive truncated: header claims more data than present");
const bytes = readMemoryRange(buf, offset, offset + size); Defensive patterns
Strategy: validation
Validate before calling
if (end > buffer.byteLength) throw new Error(`need [${start}, ${end}) but buffer is ${buffer.byteLength} bytes`); Type guard
function fitsBuffer(buffer: Uint8Array, start: number, end: number): boolean {
return end <= buffer.byteLength;
} Try / catch
try {
const bytes = readMemoryRange(buf, start, end);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes("truncated data")) {
throw new Error(`archive truncated: wanted ${end} bytes, buffer has ${buf.byteLength}`);
}
throw err;
} Prevention
- Verify the full file was downloaded/copied (compare against expected size or checksum)
- Bounds-check header-declared offsets and sizes against the buffer length
- Do not parse archives still being written or transferred
When it happens
Trigger: Calling `readMemoryRange`/`read` (via `memoryByteSource`) with end > buffer.byteLength — e.g. an archive header declares an entry at offset X with size Y but the actual buffer is shorter, or the caller computed a range past EOF.
Common situations: Parsing a truncated download or copy where the file was cut short; archive header offsets from a corrupt/tooled index pointing past EOF; reading a small fixture with ranges copied from a larger archive.
Related errors
- Truncated embedded addon archive entry: ${filename}
- Invalid ARJ ${field}: missing terminator
- Invalid ARJ method-4 compressed data: truncated bitstream
- Invalid ARJ local header
- Invalid ARJ archive: no members
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/4e45f7ba008cbd57.
Report an issue: GitHub.