can1357/oh-my-pi · error · ArchiveError

Archive member '${formatArchivePathForError(memberPath)}' is

Error message

Archive member '${formatArchivePathForError(memberPath)}' is truncated

What it means

TarMemberSource.read() checks that the requested size does not extend past the end of the in-memory tar buffer relative to the member's data offset. This throw means the tar header declared a member larger than the actual bytes remaining in the archive — the archive is truncated or corrupted. It fires before slicing, so no short data is ever silently returned.

Source

Thrown at packages/utils/src/ar/tar.ts:74

class TarMemberSource implements MemberSource {
	readonly #buffer: Uint8Array;
	readonly #dataOffset: number;
	readonly #sparse: boolean;

	constructor(buffer: Uint8Array, dataOffset: number, sparse: boolean) {
		this.#buffer = buffer;
		this.#dataOffset = dataOffset;
		this.#sparse = sparse;
	}

	async read(size: number, memberPath: string): Promise<Uint8Array> {
		if (this.#sparse) {
			throw new ArchiveError(
				`Archive member '${formatArchivePathForError(memberPath)}' is a sparse file and cannot be read`,
			);
		}
		if (size > this.#buffer.byteLength - this.#dataOffset) {
			throw new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' is truncated`);
		}
		const bytes = this.#buffer.subarray(this.#dataOffset, this.#dataOffset + size);
		if (bytes.byteLength !== size) {
			throw new ArchiveError(`Archive member '${formatArchivePathForError(memberPath)}' has an invalid size`);
		}
		return bytes;
	}
}

function readTarString(buffer: Uint8Array, offset: number, length: number): string {
	const limit = Math.min(offset + length, buffer.byteLength);
	let end = offset;
	while (end < limit && buffer[end] !== 0) end++;
	return TEXT_DECODER.decode(buffer.subarray(offset, end));
}

function bytesEqualAscii(bytes: Uint8Array, value: string): boolean {
	return bytes.byteLength === value.length && bytesMatchAscii(bytes, 0, value);

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-obtain the archive (re-download/re-copy) and verify its size/checksum against the source manifest
  2. Validate the whole tar by walking all headers before extraction to catch truncation early
  3. Check the tar size field for corruption if the file size matches expectations but reads still fail
  4. Surface a user-facing 'archive is incomplete' message instead of retrying reads against a truncated buffer

Example fix

// before: extracting without integrity check
const reader = await readTar(await readAllBytes(src));
await extractAll(reader);
// after: verify total size against manifest before parsing
if (bytes.byteLength !== manifest["bundle.tar"].size) {
	throw new Error("tar truncated; re-download");
}
const reader = await readTar(bytes);
Defensive patterns

Strategy: validation

Validate before calling

const stat = await fs.stat(path);
if (manifest[archiveName] && stat.size !== manifest[archiveName].size) {
	throw new Error(`tar truncated on disk (${stat.size} != ${manifest[archiveName].size}); re-download`);
}

Try / catch

try {
	const data = await entry.source.read(entry.size, entry.path);
} catch (err) {
	if (err instanceof ArchiveError && err.message.includes("is truncated")) {
		throw new Error(`tar is incomplete; member ${entry.path} extends past end of file — re-obtain the archive`, { cause: err });
	}
	throw err;
}

Prevention

When it happens

Trigger: Reading a tar member whose header size field exceeds buffer.byteLength - dataOffset: the archive download/copy was cut short, the file was truncated at rest, or a corrupt size field (bad octal/PAX size) in the header inflates the declared size.

Common situations: Interrupted downloads or scp/rsync transfers; tar files concatenated or edited incorrectly; archives stored on failing disks; sizes corrupted by a writer bug (non-terminated octal fields) making a member appear larger than the file.

Related errors


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