can1357/oh-my-pi · error · ArchiveError
Invalid ${this.#label} Huffman code
Error message
Invalid ${this.#label} Huffman code What it means
CanonicalHuffman.decode() walks the binary tree one bit at a time; if the next-bit child link is negative (node < 0), the bit sequence read so far is not a prefix of any code in the table. The library throws ArchiveError because the encoded data stream no longer matches the declared table — the archive data is corrupt or misaligned.
Source
Thrown at packages/utils/src/ar/lzh.ts:126
else tree.#one[node] = child;
}
node = child;
}
if (tree.#symbol[node]! >= 0 || tree.#zero[node]! >= 0 || tree.#one[node]! >= 0) {
throw new ArchiveError(`Invalid ${label} Huffman table: duplicate code`);
}
tree.#symbol[node] = symbol;
}
return tree;
}
decode(reader: MsbBitReader): number {
let node = 0;
for (let depth = 0; depth <= 16; depth++) {
const symbol = this.#symbol[node]!;
if (symbol >= 0) return symbol;
node = reader.read(1) === 0 ? this.#zero[node]! : this.#one[node]!;
if (node < 0) throw new ArchiveError(`Invalid ${this.#label} Huffman code`);
}
throw new ArchiveError(`Invalid ${this.#label} Huffman code: excessive depth`);
}
}
function readCodeLength(reader: MsbBitReader, label: string): number {
let length = reader.read(3);
if (length === 7) {
while (reader.read(1) !== 0) {
length++;
if (length > 16) throw new ArchiveError(`Invalid ${label} Huffman table: code is too long`);
}
}
return length;
}
function readTemporaryTree(reader: MsbBitReader, label: string): CanonicalHuffman {
const symbolCount = 19;View on GitHub (pinned to 9690622007)
Solutions
- Check the archive integrity (CRC / unlha -t) and re-download or restore from backup
- Verify the compressed-data offset equals headerSize + 2 (method+checksum) for the LH variant in use
- Stop decoding at the declared compressed size instead of running to end-of-input
- Catch ArchiveError and report a corrupt archive; retrying will not help
Example fix
// before: decoding until reader is exhausted decodeUntilEnd(bitReader, tree); // after: respect the declared compressed size const end = offset + compressedSize; while (bitReader.byteOffset < end) output.push(tree.decode(bitReader));
Defensive patterns
Strategy: try-catch
Validate before calling
// Bounds-check against the declared compressed size before decoding:
if (data.length < declaredCompressedSize) {
throw new Error('truncated archive: data shorter than declared size');
} Try / catch
try {
const out = decompressLhStatic(data, originalSize);
} catch (err) {
if (err instanceof ArchiveError) {
throw new Error(`archive data does not match its Huffman table: ${err.message}`);
}
throw err;
} Prevention
- Verify file integrity (size + CRC) before decompression
- Stop decoding at the declared compressed size, not end-of-buffer
- Recheck header parsing so the data section starts at the right byte
- Treat decode failures as fatal corruption; never retry the same bytes
When it happens
Trigger: Calling decompressLhStatic / temporary decoding on data where the compressed bitstream diverges from the table: corruption inside the data section, reading the data section at a wrong offset (bad header skip), or continuing to decode past the true end of the block.
Common situations: Truncated downloads cut mid-block, archives with wrong declared compressed sizes, byte-shifted parsing after a header bug, fuzz inputs.
Related errors
- Invalid ${this.#label} Huffman code: excessive depth
- Invalid ${label} Huffman table: oversubscribed codes
- Invalid ${label} Huffman table: incomplete codes
- Invalid ${label} Huffman table: prefix collision
- Invalid ${label} Huffman table: duplicate code
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/398627d002325660.
Report an issue: GitHub.