can1357/oh-my-pi · error · ArchiveError

Invalid LZH archive: header did not advance

Error message

Invalid LZH archive: header did not advance

What it means

LZH members are chained: each header's nextOffset (dataStart + packedSize) must strictly advance past the current offset, otherwise the parser would loop forever. When a header's computed packed size is zero or wraps backwards, the chain cannot progress and the archive is declared corrupt.

Source

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

	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));
			const targetPath = normalizeArchiveEntryPath(header.path.slice(separator + 1));
			if (!path || !targetPath) continue;
			entries.push({
				path,
				isDirectory: false,
				size: 0,
				mtimeMs: header.mtimeMs,
				mode: header.mode,
				storage: { type: "link", targetPath, resolveTarget: false },
			});
			continue;

View on GitHub (pinned to 9690622007)

Solutions

  1. Validate the archive with an external tool (lha l) to see which member is corrupt.
  2. Re-download or restore the archive from backup and re-verify its checksum.
  3. Extract recoverable members up to the corruption point if partial content is acceptable (parse manually member by member).
  4. Reject untrusted input that triggers this error — it usually indicates malicious or badly damaged data.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const entries = await readLzh(source, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message === "Invalid LZH archive: header did not advance") {
    throw new Error("archive chain corrupt at a member boundary — restore from backup");
  }
  throw err;
}

Prevention

When it happens

Trigger: readLzh() on an archive where a member's nextOffset <= current offset: packedSize computed as 0 or negative-to-wrap, corrupt size fields, or an extended-header packedSize override of 0.

Common situations: Corrupted or truncated archives; crafted inputs targeting parser infinite loops; archives damaged by bit-flips on storage.

Related errors


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