can1357/oh-my-pi · error · ArchiveError
Invalid ${label} Huffman table: prefix collision
Error message
Invalid ${label} Huffman table: prefix collision What it means
While inserting each symbol's code path into the binary decoding tree, build() finds an interior node already occupied by a symbol (tree.#symbol[node] >= 0) — meaning a previously inserted code is a prefix of the current one. This indicates the length table, though internally consistent by the oversubscription check, does not match canonical assignment invariants (corrupt stream or encoding bug), so decoding would be ambiguous.
Source
Thrown at packages/utils/src/ar/lzh.ts:98
if (code + counts[length]! > 2 ** length) {
throw new ArchiveError(`Invalid ${label} Huffman table: oversubscribed codes`);
}
nextCodes[length] = code;
}
if (nextCodes[maximumLength]! + counts[maximumLength]! !== 2 ** maximumLength) {
throw new ArchiveError(`Invalid ${label} Huffman table: incomplete codes`);
}
const tree = new CanonicalHuffman(label);
for (let symbol = 0; symbol < symbolCount; symbol++) {
const length = lengths[symbol]!;
if (length === 0) continue;
const symbolCode = nextCodes[length]!;
nextCodes[length] = symbolCode + 1;
let node = 0;
for (let bitIndex = length - 1; bitIndex >= 0; bitIndex--) {
if (tree.#symbol[node]! >= 0) {
throw new ArchiveError(`Invalid ${label} Huffman table: prefix collision`);
}
const bit = (symbolCode >>> bitIndex) & 1;
let child = bit === 0 ? tree.#zero[node]! : tree.#one[node]!;
if (child < 0) {
child = tree.#symbol.length;
tree.#zero.push(-1);
tree.#one.push(-1);
tree.#symbol.push(-1);
if (bit === 0) tree.#zero[node] = child;
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;
}View on GitHub (pinned to 9690622007)
Solutions
- Validate the archive externally (unlha -t / CRC check) and re-obtain a clean copy
- Ensure the table section is being read from the right bit offset (verify method id and header-skip logic before decompressLhStatic)
- If writing an encoder, always assign lengths in canonical symbol order from a standard Huffman construction
- Catch ArchiveError; do not retry decoding corrupted bytes
Defensive patterns
Strategy: try-catch
Try / catch
try {
const out = decompressLhStatic(data, size);
} catch (err) {
if (err instanceof ArchiveError) return { ok: false, reason: 'corrupt-huffman-table' };
throw err;
} Prevention
- Validate archives with an independent tool before processing
- Ensure bit-reader alignment: correct header size and data offset
- Use standard Huffman length construction in encoders
- Do not attempt recovery decoding on prefix-collision tables
When it happens
Trigger: Decompressing LZH data whose command/position/temporary table lengths were corrupted after passing the count checks (e.g. bit-rot altering lengths without breaking the Kraft sum), or a custom encoder emitting non-canonical length assignments.
Common situations: Damaged archives, hand-edited or fuzzed table sections, mixing compressed blocks from different variants.
Related errors
- Invalid ${label} Huffman table: oversubscribed codes
- Invalid ${label} Huffman table: incomplete codes
- Invalid ${label} Huffman table: duplicate code
- Invalid ${this.#label} Huffman code
- Invalid ${this.#label} Huffman code: excessive depth
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/34a15641070ef79c.
Report an issue: GitHub.