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

  1. Check the buffer length against the archive's declared total size before parsing
  2. Re-download or re-extract the file — truncation is the usual root cause
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4e45f7ba008cbd57. Report an issue: GitHub.