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.

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.

Appendix: 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)