can1357/oh-my-pi · error · ArchiveError
Truncated compress (.Z) header
Error message
Truncated compress (.Z) header
What it means
A valid compress (.Z) stream begins with the 2-byte magic 0x1F 0x9D followed by a flags byte — at least 3 bytes total. This error is thrown when the input buffer is shorter than 3 bytes, so no header can even be read. (A 3+ byte input with wrong magic throws the sibling 'Invalid compress (.Z) header' instead.)
Source
Thrown at packages/utils/src/ar/codecs/lzw.ts:101
if (needed <= this.#bytes.byteLength) {
return;
}
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);View on GitHub (pinned to 9690622007)
Solutions
- Check bytes.byteLength >= 3 (ideally the 0x1F9D magic) before calling lzwDecompress.
- Verify the source file is non-empty and is actually a .Z file (`file data.Z`).
- Fix the slicing/read logic that produced the short buffer.
- Confirm you're not passing a wrong variable (e.g. a small header buffer instead of the full payload).
Example fix
// before
const data = await Bun.file(path).bytes();
lzwDecompress(data, max);
// after
const data = await Bun.file(path).bytes();
if (data.byteLength < 3 || data[0] !== 0x1f || data[1] !== 0x9d) throw new Error(`Not a compress (.Z) file: ${path}`);
lzwDecompress(data, max); Defensive patterns
Strategy: type-guard
Validate before calling
function looksLikeCompressZ(bytes: Uint8Array): boolean {
return bytes.byteLength >= 3 && bytes[0] === 0x1f && bytes[1] === 0x9d;
} Type guard
function isCompressZ(v: Uint8Array): v is Uint8Array & { length: 3 } {
return v.byteLength >= 3 && v[0] === 0x1f && v[1] === 0x9d;
} Try / catch
try {
return lzwDecompress(bytes, maxOutput);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes("header")) {
throw new Error(`Input is not a compress (.Z) stream (${bytes.byteLength} bytes)`, { cause: err });
}
throw err;
} Prevention
- Check the 0x1F 0x9D magic before decompressing; route non-.Z inputs to the right decoder by magic byte.
- Reject empty/undersized files at ingestion time.
- Confirm the variable passed to lzwDecompress is the full payload, not a header fragment.
- Handle zero-byte reads from streams as errors, not valid inputs.
When it happens
Trigger: Calling lzwDecompress with an empty, 1-byte, or 2-byte buffer — e.g. an empty file, a mis-sliced subarray, or reading zero bytes from a stream.
Common situations: Empty-file upload accepted without size checks; wrong path/column sliced from a container; a read that returned 0 bytes due to an IO error treated as success; passing the wrong variable to the decompressor.
Related errors
- Truncated compress (.Z) code group
- Invalid non-zero compress (.Z) padding
- Compress (.Z) output exceeds the ${this.#limit}-byte limit
- Invalid compress (.Z) output limit
- ${error instanceof Error ? error.message : String(error)}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/725105ee502a15df.
Report an issue: GitHub.