can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: missing block header

Error message

Invalid XZ stream: missing block header

What it means

When decoding an XZ block the library expects to find a valid block header at the current offset: the offset must be inside the buffer and the first byte (header size indicator) must be non-zero (0 is the special 'no block' marker for padding). It throws when the offset is past the end of the buffer or the byte there is 0, meaning the block header is absent from where the index said it would be.

Source

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

	if (checkId === 0) return;
	if (checkId === 1) {
		if (read32LE(expected, 0) !== crc32(output)) throw new ArchiveError("Invalid XZ stream: block CRC32 mismatch");
		return;
	}
	if (checkId === 4) {
		const actual = crc64(output);
		let stored = 0n;
		for (let index = 0; index < 8; index++) stored |= BigInt(expected[index]!) << BigInt(index * 8);
		if (actual !== stored) throw new ArchiveError("Invalid XZ stream: block CRC64 mismatch");
		return;
	}
	const actual = new Uint8Array(new Bun.CryptoHasher("sha256").update(output).digest());
	if (!equalBytes(actual, expected)) throw new ArchiveError("Invalid XZ stream: block SHA-256 mismatch");
}

async function decodeBlock(bytes: Uint8Array, offset: number, record: XzRecord, checkId: number): Promise<Uint8Array> {
	if (offset >= bytes.byteLength || bytes[offset] === 0)
		throw new ArchiveError("Invalid XZ stream: missing block header");
	const headerSize = (bytes[offset]! + 1) * 4;
	if (offset + headerSize > bytes.byteLength || headerSize < 8)
		throw new ArchiveError("Invalid XZ stream: truncated block header");
	if (crc32(bytes.subarray(offset, offset + headerSize - 4)) !== read32LE(bytes, offset + headerSize - 4))
		throw new ArchiveError("Invalid XZ stream: block header CRC32 mismatch");
	const cursor: Cursor = { bytes, pos: offset + 1, limit: offset + headerSize - 4 };
	const flags = bytes[cursor.pos++]!;
	if ((flags & 0x3c) !== 0) throw new ArchiveError("Unsupported XZ block flags");
	const filterCount = (flags & 3) + 1;
	const declaredCompressed = (flags & 0x40) !== 0 ? readVarInt(cursor) : undefined;
	const declaredUncompressed = (flags & 0x80) !== 0 ? readVarInt(cursor) : undefined;
	const filters: XzFilter[] = [];
	for (let index = 0; index < filterCount; index++) {
		const id = readVarInt(cursor);
		const propertySize = readVarInt(cursor);
		if (propertySize > cursor.limit - cursor.pos)
			throw new ArchiveError("Invalid XZ stream: truncated filter properties");
		filters.push({ id, properties: bytes.slice(cursor.pos, cursor.pos + propertySize) });

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-obtain the complete file — check its size against the source and re-download if truncated
  2. Verify externally with `xz -t`; if it fails there too the file is damaged
  3. Ensure you're passing the entire .xz file to the decoder (not a partial slice/stream chunk)
  4. Reject zero-padded or concatenated non-standard files; split them properly before decoding

Example fix

// before: decoding a partial chunk
const buf = new Uint8Array(await firstChunkOnly());
await decodeXz(buf);
// after: read the whole file
const buf = new Uint8Array(await Bun.file('archive.xz').arrayBuffer());
await decodeXz(buf);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the whole file is present before decoding
const stat = await Bun.file('archive.xz').stat?.() ?? null;
const bytes = new Uint8Array(await Bun.file('archive.xz').arrayBuffer());
if (bytes.byteLength < 32) throw new Error('File too small to be a complete XZ stream');

Type guard

null

Try / catch

try {
  return await decodeXz(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('missing block header')) {
    throw new Error('XZ stream is truncated or contains unexpected padding; re-obtain the complete file');
  }
  throw err;
}

Prevention

When it happens

Trigger: Decoding a truncated XZ stream (buffer ends before the block header); an XZ stream where padding/misalignment zeros appear where a block header should start; a corrupted index pointing at the wrong offset.

Common situations: Files cut off mid-transfer; archives concatenated or padded incorrectly; storage corruption zeroing out header bytes.

Related errors


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