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.

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.

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