can1357/oh-my-pi · error · ArchiveError

Invalid compress (.Z) header

Error message

Invalid compress (.Z) header

What it means

lzwDecompress() only decodes ncompress-style Unix compress (.Z) streams, which must begin with the two magic bytes 0x1f 0x9d followed by a flags byte. This error means the input does not start with that magic signature, so the bytes are not a .Z stream at all (or the first two bytes were lost/corrupted in transit).

Source

Thrown at packages/utils/src/ar/codecs/lzw.ts:104

		let capacity = Math.max(needed, Math.min(this.#limit, Math.max(64, this.#bytes.byteLength * 2)));
		if (capacity > this.#limit) {
			capacity = this.#limit;
		}
		const grown = new Uint8Array(capacity);
		grown.set(this.#bytes.subarray(0, this.#length));
		this.#bytes = grown;
	}
}

function decode(bytes: Uint8Array, maxOutput: number): Uint8Array {
	if (!Number.isSafeInteger(maxOutput) || maxOutput < 0) {
		throw new ArchiveError("Invalid compress (.Z) output limit");
	}
	if (bytes.byteLength < 3) {
		throw new ArchiveError("Truncated compress (.Z) header");
	}
	if (bytes[0] !== 0x1f || bytes[1] !== 0x9d) {
		throw new ArchiveError("Invalid compress (.Z) header");
	}

	const flags = bytes[2]!;
	if ((flags & 0x60) !== 0) {
		throw new ArchiveError("Unsupported compress (.Z) header flags");
	}
	const maxBits = flags & 0x1f;
	if (maxBits < MIN_BITS || maxBits > MAX_BITS) {
		throw new ArchiveError(`Invalid compress (.Z) maximum code width ${maxBits}`);
	}
	const blockMode = (flags & 0x80) !== 0;
	const dictionaryLimit = 2 ** maxBits;
	const parents = new Uint16Array(dictionaryLimit);
	const suffixes = new Uint8Array(dictionaryLimit);
	const stack = new Uint8Array(dictionaryLimit);
	const reader = new LsbCodeReader(bytes.subarray(3));
	const output = new BoundedOutput(maxOutput, bytes.byteLength);

View on GitHub (pinned to 9690622007)

Solutions

  1. Call isCompressZ(bytes) first and reject/handle non-matching input instead of decompressing blindly.
  2. Verify the file is actually a compress (.Z) file (file(1) or first bytes 1f 9d); if it is gzip/zlib/deflate, use the matching codec instead.
  3. Check that the buffer passed starts at offset 0 of the stream — no leading metadata or partial read that skipped the header.
  4. Re-download or re-extract the file if the header bytes are corrupt (corruption during transfer).

Example fix

// before
const out = await lzwDecompress(data, 10_000_000);
// after
import { isCompressZ, lzwDecompress } from "@oh-my-pi/pi-utils";
if (!isCompressZ(data)) throw new Error("not a .Z stream");
const out = await lzwDecompress(data, 10_000_000);
Defensive patterns

Strategy: validation

Validate before calling

import { isCompressZ } from "@oh-my-pi/pi-utils";
if (!isCompressZ(bytes)) throw new Error("Input is not a compress (.Z) stream: expected magic 0x1f 0x9d");

Type guard

function isCompressZStream(bytes: Uint8Array): boolean {
  return bytes.byteLength >= 3 && bytes[0] === 0x1f && bytes[1] === 0x9d;
}

Try / catch

try {
  const out = await lzwDecompress(bytes, maxOutput);
} catch (e) {
  if (e instanceof ArchiveError && e.message.includes("Invalid compress (.Z) header")) {
    throw new Error("File is not a .Z archive — check format/extension");
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling lzwDecompress(bytes, maxOutput) with a Uint8Array whose bytes[0] !== 0x1f or bytes[1] !== 0x9d — e.g. passing a gzip (0x1f 0x8b), zlib (0x78), bzip2 ('BZ'), or raw LZW stream, or a .Z file whose header bytes were stripped by an upload/transfer step.

Common situations: Handing the function a file with a renamed .Z extension that is actually another format; concatenating or re-assembling archive members and off-by-one slicing away the 3-byte header; base64/text-mode transfers corrupting the first bytes; trying to decompress an already-uncompressed payload.

Related errors


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