can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: block size does not match its index recor

Error message

Invalid XZ stream: block size does not match its index record

What it means

This ArchiveError is thrown while decoding an XZ block when the block's actual end (check data end) does not line up with the padded block size recorded in the stream index. The index record's unpaddedSize (rounded up to a 4-byte boundary) must exactly match where the block's integrity check ends; a mismatch means the stream is corrupt or the index/block pairing is broken.

Source

Thrown at packages/utils/src/ar/codecs/xz.ts:483

	if (checkStart + integritySize > bytes.byteLength) throw new ArchiveError("Invalid XZ stream: truncated block data");
	const last = filters[filters.length - 1]!;
	if (last.id !== 0x21 || last.properties.byteLength !== 1)
		throw new ArchiveError(`Unsupported XZ terminal filter ID 0x${last.id.toString(16)} (LZMA2 required)`);
	let output = await lzma2Decompress(
		last.properties[0]!,
		bytes.subarray(compressedStart, compressedEnd),
		record.uncompressedSize,
	);
	if (output.byteLength !== record.uncompressedSize)
		throw new ArchiveError("Invalid XZ stream: decoded block size mismatch");
	output = output.slice();
	for (let index = filters.length - 2; index >= 0; index--) applyFilter(output, filters[index]!);
	for (let index = compressedEnd; index < checkStart; index++)
		if (bytes[index] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero block padding");
	verifyCheck(checkId, output, bytes.subarray(checkStart, checkStart + integritySize));
	const paddedEnd = offset + Math.ceil(record.unpaddedSize / 4) * 4;
	if (checkStart + integritySize !== paddedEnd)
		throw new ArchiveError("Invalid XZ stream: block size does not match its index record");
	return output;
}

/** Whether bytes begin with the XZ stream-header magic. */
export function isXz(bytes: Uint8Array): boolean {
	return bytes.byteLength >= XZ_MAGIC.byteLength && equalBytes(bytes.subarray(0, XZ_MAGIC.byteLength), XZ_MAGIC);
}

/** Decompress all concatenated streams in an XZ container within `maxOutput`. */
export async function xzDecompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {
	if (!Number.isSafeInteger(maxOutput) || maxOutput < 0) throw new ArchiveError("Invalid XZ output limit");
	try {
		const streams = discoverStreams(bytes);
		let totalSize = 0;
		for (const stream of streams)
			for (const record of stream.records) {
				totalSize += record.uncompressedSize;
				if (!Number.isSafeInteger(totalSize) || totalSize > maxOutput)

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download or re-extract the archive from a trusted source
  2. Verify the file's checksum against the publisher's published hash
  3. Test the file with a standalone tool (xz -t) to confirm corruption before reporting a bug
  4. If concatenating XZ streams yourself, keep each stream's blocks and index intact

Example fix

// before: trusting a partially downloaded file
await xzDecompress(truncatedBytes, limit);
// after: validate integrity first
if ((await Bun.file(path).slice(0).arrayBuffer()) && !(await fileChecksumMatches(path))) throw new Error('archive corrupt');
await xzDecompress(bytes, limit);
Defensive patterns

Strategy: validation

Validate before calling

import { isXz } from '@oh-my-pi/pi-utils/ar/codecs/xz';
if (!isXz(bytes)) throw new Error('input is not XZ');
// trust boundary: pre-check integrity out of band (xz -t or published checksum)

Try / catch

try {
  const out = await xzDecompress(bytes, limit);
} catch (err) {
  if (err instanceof ArchiveError && /block size does not match/.test(err.message)) {
    // treat as corrupt archive: surface to user / re-fetch
  } else throw err;
}

Prevention

When it happens

Trigger: Calling xzDecompress (or decoding a .tar.xz/.xz payload) on bytes whose block header/check region and index records disagree — e.g. corrupted archive data, a hand-truncated or spliced XZ file, or an index from a different stream.

Common situations: Partially downloaded or truncated .xz files; byte-level edits to archives; concatenated streams where a block from one stream is paired with an index from another; files damaged in transfer.

Related errors


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