can1357/oh-my-pi · error · ArchiveError

Truncated compress (.Z) code group

Error message

Truncated compress (.Z) code group

What it means

compress (.Z) codes are read in groups whose width starts at 9 bits and grows as the code table expands; alignCodeGroup snaps the bit position to the next width-byte boundary. This error is thrown when the computed group boundary lies beyond the end of the input, meaning the stream was cut off before the current code group completed.

Source

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

	read(width: number): number | undefined {
		if (this.remainingBits < width) {
			return undefined;
		}
		let value = 0;
		for (let bit = 0; bit < width; bit++) {
			const position = this.#bitPosition + bit;
			value += ((this.#bytes[position >>> 3]! >>> (position & 7)) & 1) * 2 ** bit;
		}
		this.#bitPosition += width;
		return value;
	}

	alignCodeGroup(width: number): void {
		const groupBits = width * 8;
		const aligned = this.#groupStart + Math.ceil((this.#bitPosition - this.#groupStart) / groupBits) * groupBits;
		if (aligned > this.#bytes.byteLength * 8) {
			throw new ArchiveError("Truncated compress (.Z) code group");
		}
		this.#bitPosition = aligned;
		this.#groupStart = aligned;
	}

	assertFinalPadding(): void {
		this.#assertZeroBits(this.#bytes.byteLength * 8);
	}

	#assertZeroBits(end: number): void {
		for (let position = this.#bitPosition; position < end; position++) {
			if (((this.#bytes[position >>> 3]! >>> (position & 7)) & 1) !== 0) {
				throw new ArchiveError("Invalid non-zero compress (.Z) padding");
			}
		}
	}
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download/re-copy the .Z file and verify its size (e.g. with `compress -c | wc -c` comparison or `zcat` test).
  2. Check the code that reads the file — ensure it reads the full file (use Bun.file().bytes() / complete readFile) rather than a partial buffer.
  3. Test the file with `zcat file.Z > /dev/null` to confirm it decompresses outside this library.
  4. If concatenating .Z members, split on member boundaries instead of appending raw compressed bytes.

Example fix

// before: partial read
const bytes = buf.subarray(0, 512);
lzwDecompress(bytes, max);
// after: full file
const bytes = new Uint8Array(await Bun.file(path).arrayBuffer());
lzwDecompress(bytes, max);
Defensive patterns

Strategy: validation

Validate before calling

if (bytes.byteLength < 3 || bytes[0] !== 0x1f || bytes[1] !== 0x9d) throw new Error("Not a compress (.Z) stream");

Try / catch

try {
  return lzwDecompress(bytes, maxOutput);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("code group")) {
    throw new Error(".Z stream is truncated (ends mid code group) — re-obtain the file", { cause: err });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling alignCodeGroup (during decode/lzwDecompress) when the remaining input bits are fewer than needed to reach the next width-aligned boundary — i.e. the input ends mid-group.

Common situations: Truncated .Z file from an incomplete download or interrupted transfer; reading fewer bytes than the compressed stream requires; corrupted archive tail.

Related errors


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