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.

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)

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.