can1357/oh-my-pi · error · ArchiveError

Invalid non-zero compress (.Z) padding

Error message

Invalid non-zero compress (.Z) padding

What it means

After the compress (.Z) end-of-stream code, the remaining bits up to the final byte boundary must be zero padding. #assertZeroBits scans each leftover bit and throws if any is non-zero. Non-zero padding means the stream did not terminate where it claims, so the input is malformed or there is extra data.

Solutions

  1. Split concatenated .Z members and decode each independently (the compress format supports concatenated members).
  2. Strip any known trailing metadata/garbage before decompressing.
  3. Verify file integrity with `zcat` / checksum comparison.
  4. Ensure the buffer passed covers exactly the compressed stream, not extra trailing bytes.

Example fix

// before: one call on concatenation
lzwDecompress(concatBytes, max);
// after: decode each .Z member
for (const member of splitZMembers(concatBytes)) lzwDecompress(member, max);
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return lzwDecompress(bytes, maxOutput);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("padding")) {
    throw new Error("Non-zero bits after .Z EOF code — likely concatenated members or trailing garbage", { cause: err });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling lzwDecompress (via assertFinalPadding) on input where bits following the EOF code are set — e.g. a second concatenated .Z member, appended garbage, or corruption in the final byte.

Common situations: Concatenated .Z archives decoded as a single stream; trailing junk after decompressed data; single-bit corruption in the last byte; hand-trimmed buffers that cut into the final code word.

Related errors


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

Appendix: source

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

	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");
			}
		}
	}
}

class BoundedOutput {
	readonly #limit: number;
	#bytes: Uint8Array;
	#length = 0;

	constructor(limit: number, inputSize: number) {
		this.#limit = limit;
		this.#bytes = new Uint8Array(Math.min(limit, Math.max(64, Math.min(inputSize * 2, 64 * 1024))));
	}

	appendReversed(stack: Uint8Array, length: number): void {
		this.#ensure(length);
		for (let index = length - 1; index >= 0; index--) {

View on GitHub (pinned to 9690622007)