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
- Validate the archive with an external tool (lha l) to see which member is corrupt.
- Re-download or restore the archive from backup and re-verify its checksum.
- Extract recoverable members up to the corruption point if partial content is acceptable (parse manually member by member).
- 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
- Validate archives with an external tool (lha l) before batch processing
- Keep checksums for stored archives and verify before parsing
- Isolate parsing of untrusted archives so a hang/corruption cannot affect the host
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
- Invalid LZH level-2 extended header chain
- Invalid LZH common header CRC-16
- Invalid ARJ archive: truncated ${what}
- Invalid ARJ ${field}: missing terminator
- Invalid ARJ basic header size
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/c51ee05e1bf9ee50.
Report an issue: GitHub.