can1357/oh-my-pi · error · ArchiveError

Bzip2 block exhausted its Huffman selectors

Error message

Bzip2 block exhausted its Huffman selectors

What it means

During bzip2 Huffman decoding, the decoder consumes 'selectors' that map groups of 50 symbols to Huffman tables. This error is thrown when the decoder needs a new selector group but selectorIndex has already run past the end of the selector list parsed from the block header. It means the compressed bitstream is inconsistent with its own header — there are more symbols to decode than the selectors can account for.

Source

Thrown at packages/utils/src/ar/codecs/bzip2.ts:282

function decodeBlockData(
	reader: BitReader,
	blockSizeLimit: number,
	usedBytes: Uint8Array,
	selectors: Uint8Array,
	tables: HuffmanTable[],
): Uint8Array {
	const mtf = new Uint8Array(usedBytes);
	const block = new Uint8Array(blockSizeLimit);
	let blockLength = 0;
	let selectorIndex = 0;
	let groupRemaining = 0;
	let table: HuffmanTable | undefined;

	const nextSymbol = (): number => {
		if (groupRemaining === 0) {
			if (selectorIndex >= selectors.length) {
				throw new ArchiveError("Bzip2 block exhausted its Huffman selectors");
			}
			table = tables[selectors[selectorIndex++]!];
			groupRemaining = GROUP_SIZE;
		}
		groupRemaining--;
		return table!.decode(reader);
	};

	const append = (byte: number, count: number): void => {
		if (count < 0 || blockLength + count > blockSizeLimit) {
			throw new ArchiveError("Bzip2 block exceeds its declared block-size level");
		}
		block.fill(byte, blockLength, blockLength + count);
		blockLength += count;
	};

	const endSymbol = usedBytes.length + 1;
	let symbol = nextSymbol();

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the .bz2 archive integrity (e.g. `bzip2 -t file.bz2`) and re-obtain or re-extract the file if corrupted.
  2. Ensure you are passing the complete block bytes to decodeBlockData — no truncation of the input Uint8Array.
  3. Check you are not slicing a multi-stream .bz2 file at incorrect stream boundaries.
  4. If the data comes from a network transfer, compare checksums/byte counts against the source.

Example fix

// before: feeding a truncated slice
const chunk = bytes.subarray(0, 1000);
decompressBzip2(chunk);
// after: pass the whole stream/complete block
decompressBzip2(bytes);
Defensive patterns

Strategy: try-catch

Validate before calling

import { ArchiveError } from "@oh-my-pi/pi-utils";
if (bytes.byteLength < 4 || !textStartsWith(bytes, "BZh")) throw new Error("Not a bzip2 stream");

Try / catch

import { ArchiveError } from "@oh-my-pi/pi-utils";
try {
  return decompressBzip2(bytes);
} catch (err) {
  if (err instanceof ArchiveError) {
    throw new Error(`Corrupt bzip2 block (selector exhaustion): re-obtain the archive`, { cause: err });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling nextSymbol (via symbol/decodeBlockData) on a bzip2 block whose selector count in the header is too small for the number of RUNA/RUNB/MTF symbols actually encoded, i.e. corrupt or truncated block data, or a block that was not fully supplied to the decoder.

Common situations: Decoding a corrupted or partially downloaded .bz2 file; a buggy bzip2 encoder; feeding the decompressor the wrong slice of a concatenated bzip2 stream; bit-level corruption from a bad transfer.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/eafa8d2330a994a2. Report an issue: GitHub.