can1357/oh-my-pi · error · ArchiveError

Unable to read LZH archive: ${error instanceof Error ? error

Error message

Unable to read LZH archive: ${error instanceof Error ? error.message : String(error)}

What it means

readLzh() must load the whole archive into memory via readAllBytes(source). If that read fails for a non-ArchiveError reason (I/O error, permission problem, stream failure), the underlying error is wrapped into this ArchiveError so callers only need to handle one error type.

Source

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

/** Probe whether bytes begin with an LZH/LHA member header. */
export function sniffLzh(bytes: Uint8Array): boolean {
	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("|");

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the wrapped message (it contains the original error text, e.g. EACCES) and fix the underlying cause.
  2. Check file permissions and that the path still exists before calling readLzh.
  3. Re-try transient I/O failures (network mounts, removable media) once the device is available.
  4. If using a custom ByteSource, verify its read implementation handles errors and streams correctly.

Example fix

// before
const entries = await readLzh(source, options);
// after
try {
  const entries = await readLzh(source, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message.startsWith("Unable to read LZH archive: ")) {
    console.error("check file exists / permissions:", err.message);
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const st = await fs.stat(path);
if (!st.isFile()) throw new Error("not a regular file");
await fs.access(path, fs.constants.R_OK); // fail early on permissions

Try / catch

try {
  const entries = await readLzh(source, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message.startsWith("Unable to read LZH archive:")) {
    // wrapped I/O error: message carries the original cause (e.g. EACCES, EIO)
    logger.error("LZH source read failed", { cause: err.message });
    throw err;
  }
  throw err;
}

Prevention

When it happens

Trigger: readLzh() with a ByteSource whose underlying read fails: unreadable file, deleted file mid-read, broken stream, network-backed source erroring, EACCES/EIO from the filesystem.

Common situations: File moved/deleted between stat and read; insufficient permissions; reading from a flaky network mount or removable drive; a custom ByteSource implementation that throws.

Related errors


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