can1357/oh-my-pi · error · ArchiveError

Invalid LZH archive: truncated data

Error message

Invalid LZH archive: truncated data

What it means

After reading all bytes, the library verifies the buffer length equals the ByteSource's declared size. A mismatch means the source shrank or reported a wrong size — the archive is incomplete relative to its own metadata, so parsing would read out of bounds.

Source

Thrown at packages/utils/src/ar/lzh.ts:612

	if (bytes.byteLength < 22 || bytes[2] !== 0x2d || bytes[6] !== 0x2d || bytes[3] !== 0x6c) return false;
	const method = String.fromCharCode(...bytes.subarray(2, 7));
	return LHA_METHOD_PATTERN.test(method) && bytes[20]! <= 2;
}

/** Index an LZH/LHA archive and lazily decode its members from the bounded archive buffer. */
export const readLzh: FormatReader = async (
	source: ByteSource,
	options: FormatReadOptions,
): Promise<ArchiveIndexEntry[]> => {
	assertInMemorySize(source.size, options.limits);
	let bytes: Uint8Array;
	try {
		bytes = await readAllBytes(source);
	} catch (error) {
		if (error instanceof ArchiveError) throw error;
		throw new ArchiveError(`Unable to read LZH archive: ${error instanceof Error ? error.message : String(error)}`);
	}
	if (bytes.byteLength !== source.size) throw new ArchiveError("Invalid LZH archive: truncated data");
	if (!sniffLzh(bytes)) throw new ArchiveError("Invalid LZH archive header");
	const entries: ArchiveIndexEntry[] = [];
	let offset = 0;
	let parsedCount = 0;
	let metadataSize = 0;
	while (offset < bytes.byteLength && bytes[offset] !== 0) {
		const header = parseLzhHeader(bytes, offset, options);
		metadataSize += header.dataStart - offset;
		assertIndexSize(metadataSize, options.limits, "index");
		assertEntryCount(++parsedCount, options.limits);
		if (header.nextOffset <= offset) throw new ArchiveError("Invalid LZH archive: header did not advance");
		offset = header.nextOffset;
		if (!header.path) continue;
		const isDirectory = header.method === "-lhd-";
		if (isDirectory && header.mode !== undefined && (header.mode & 0xf000) === 0xa000) {
			const separator = header.path.indexOf("|");
			if (separator < 1) throw new ArchiveError(`Invalid LZH symbolic link '${header.path}'`);
			const path = normalizeArchiveEntryPath(header.path.slice(0, separator));

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download or re-copy the archive and confirm the size matches the origin.
  2. Rebuild the ByteSource from a fresh stat so size matches current file length.
  3. Avoid reading archives while they are still being written; wait for the producer to finish (e.g. tmpfile + rename pattern).
  4. Verify the archive's integrity with a checksum before indexing.

Example fix

// before: size captured earlier, file changed since
const source = makeByteSource(path, staleSize);
const entries = await readLzh(source, options);
// after: refresh size immediately before reading
const size = (await fs.stat(path)).size;
const source = makeByteSource(path, size);
const entries = await readLzh(source, options);
Defensive patterns

Strategy: validation

Validate before calling

const st = await fs.stat(path);
const bytes = await readAllBytes(source);
if (bytes.length !== st.size) throw new Error("file changed or truncated during read — retry with a fresh stat");

Try / catch

try {
  const entries = await readLzh(source, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message === "Invalid LZH archive: truncated data") {
    throw new Error("archive incomplete — re-download or wait for the writer to finish");
  }
  throw err;
}

Prevention

When it happens

Trigger: readLzh() where readAllBytes() returns fewer bytes than source.size: file truncated during download/copy, concurrent writer shrinking the file, ByteSource.size stale after the file changed, partial transfer.

Common situations: Interrupted downloads; rsync/ftp copy cut short; another process rewriting the .lzh while it is being indexed; custom ByteSource built from a stale stat().

Related errors


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