can1357/oh-my-pi · error · ArchiveError
Invalid LZH archive header
Error message
Invalid LZH archive header
What it means
sniffLzh() checks the opening bytes look like an LZH member header: at least 22 bytes, the method string starting with '-l' at offset 2 and ending '-' at offset 6, a valid compression-method pattern, and header level <= 2. Files that fail are not LZH at all, so the reader refuses them before any parsing.
Source
Thrown at packages/utils/src/ar/lzh.ts:613
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));
const targetPath = normalizeArchiveEntryPath(header.path.slice(separator + 1));View on GitHub (pinned to 9690622007)
Solutions
- Confirm the file's actual type (file(1) command or magic bytes) and use the correct format reader.
- If it is a self-extracting or wrapped LHA (BinHex, MacBinary), strip the wrapper first.
- Re-download if the 'archive' is actually an HTML/text error page.
- Call sniffLzh() yourself (exported) to gate before invoking readLzh.
Example fix
// before
const entries = await readLzh(source, options);
// after
const bytes = await readAllBytes(source);
if (!sniffLzh(bytes)) throw new Error("not an LZH archive — check the file type");
const entries = await readLzh(source, options); Defensive patterns
Strategy: type-guard
Validate before calling
const bytes = await readAllBytes(source);
if (!sniffLzh(bytes)) throw new Error("not an LZH archive — check the file type with `file`"); Type guard
function looksLikeLzh(buf: Uint8Array): boolean {
return sniffLzh(buf);
} Try / catch
try {
const entries = await readLzh(source, options);
} catch (err) {
if (err instanceof ArchiveError && err.message === "Invalid LZH archive header") {
throw new Error("input is not LZH — detect the real format (zip/gzip/7z?) and use the right reader");
}
throw err;
} Prevention
- Detect archive format by magic bytes, never by file extension
- Gate format dispatch with the exported sniffLzh() helper
- Handle SFX/wrapped LHA (MacBinary, BinHex) by stripping wrappers first
When it happens
Trigger: readLzh() called on bytes that are not an LZH/LHA archive: a different archive format (zip, gzip, 7z), a plain text/binary file, or an empty file renamed to .lzh.
Common situations: Wrong format passed by mistake; server returned an HTML error page saved as .lzh; MacBinary/BinHex wrapper around an LHA file; file extension renamed without conversion.
Related errors
- Invalid ARJ archive header
- Invalid CPIO archive: unsupported or corrupt magic at offset
- Invalid ${label} Huffman table: oversubscribed codes
- Invalid ${label} Huffman table: incomplete codes
- Invalid ${label} Huffman table: prefix collision
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1d0090324de21207.
Report an issue: GitHub.