{"record":{"id":"eb8968b8a48b0eee","repo":"can1357/oh-my-pi","slug":"invalid-label-huffman-table-incomplete-codes","errorCode":null,"errorMessage":"Invalid ${label} Huffman table: incomplete codes","messagePattern":"Invalid (.+?) Huffman table: incomplete codes","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":86,"sourceCode":"\t\t\tif (length > 16) throw new ArchiveError(`Invalid ${label} Huffman table: code is too long`);\n\t\t\tif (length !== 0) {\n\t\t\t\tcounts[length]++;\n\t\t\t\tmaximumLength = Math.max(maximumLength, length);\n\t\t\t}\n\t\t}\n\t\tif (maximumLength === 0) throw new ArchiveError(`Invalid ${label} Huffman table: no symbols`);\n\n\t\tconst nextCodes = new Uint32Array(17);\n\t\tlet code = 0;\n\t\tfor (let length = 1; length <= 16; length++) {\n\t\t\tcode = (code + counts[length - 1]!) * 2;\n\t\t\tif (code + counts[length]! > 2 ** length) {\n\t\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: oversubscribed codes`);\n\t\t\t}\n\t\t\tnextCodes[length] = code;\n\t\t}\n\t\tif (nextCodes[maximumLength]! + counts[maximumLength]! !== 2 ** maximumLength) {\n\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: incomplete codes`);\n\t\t}\n\n\t\tconst tree = new CanonicalHuffman(label);\n\t\tfor (let symbol = 0; symbol < symbolCount; symbol++) {\n\t\t\tconst length = lengths[symbol]!;\n\t\t\tif (length === 0) continue;\n\t\t\tconst symbolCode = nextCodes[length]!;\n\t\t\tnextCodes[length] = symbolCode + 1;\n\t\t\tlet node = 0;\n\t\t\tfor (let bitIndex = length - 1; bitIndex >= 0; bitIndex--) {\n\t\t\t\tif (tree.#symbol[node]! >= 0) {\n\t\t\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: prefix collision`);\n\t\t\t\t}\n\t\t\t\tconst bit = (symbolCode >>> bitIndex) & 1;\n\t\t\t\tlet child = bit === 0 ? tree.#zero[node]! : tree.#one[node]!;\n\t\t\t\tif (child < 0) {\n\t\t\t\t\tchild = tree.#symbol.length;\n\t\t\t\t\ttree.#zero.push(-1);","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L68-L104","documentation":"After the length loop, CanonicalHuffman.build() requires the canonical codes to exactly fill the code space: firstCode[maximumLength] + counts[maximumLength] === 2^maximumLength. If the table is under-subscribed (unused code space remains), the tree would contain paths that decode to nothing, so the library rejects it. This catches archives whose declared code lengths do not form a complete Huffman code.","triggerScenarios":"Decompressing an LZH stream where the encoded symbol-count/length distribution is short by symbols — e.g. a truncated table section, an encoder that wrote fewer lengths than it declared, or corruption zeroing out trailing lengths in readTemporaryTree/readCommandTree/readPositionTree.","commonSituations":"Bit-flipped or truncated .lzh downloads, archives produced by buggy third-party compressors, mixing LH-format variants (LH5 table parsed as LH7), or fuzzing.","solutions":["Re-extract or re-download the archive; compare file size and CRC","Confirm you are using the matching decompress routine for the archive's method byte (LH5 vs LH6 vs LH7 differ in table sizes)","If you are the encoder, emit lengths for every nonzero-frequency symbol so the code space is exactly filled","Catch ArchiveError and report corruption instead of attempting partial decode"],"exampleFix":"// before: emitting only symbols actually used, leaving space unfilled\nwriteLengths(freqs.filter(f => f > 0));\n// after: use a complete-code canonical assignment\nwriteLengths(canonicalCompleteLengths(freqs, 16)); // guarantees Kraft sum == 1","handlingStrategy":"try-catch","validationCode":"// Validate the archive externally before decoding:\n// unlha t archive.lzh  (exit 0 => structurally sound)","typeGuard":null,"tryCatchPattern":"try {\n  const out = decompressLhStatic(data, size);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('incomplete codes')) {\n    throw new Error('archive code-length table is incomplete; file is corrupt');\n  }\n  throw err;\n}","preventionTips":["Re-download corrupted archives rather than forcing extraction","Match decompressor variant to the archive's method id","If you are the encoder, guarantee a complete canonical code (Kraft sum == 1)","Fail fast on first ArchiveError instead of partial decoding"],"tags":["archive","huffman","lzh","corruption","validation"],"backgroundTag":"corrupt-huffman-table","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}