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
- Re-obtain the complete file — check its size against the source and re-download if truncated
- Verify externally with `xz -t`; if it fails there too the file is damaged
- Ensure you're passing the entire .xz file to the decoder (not a partial slice/stream chunk)
- 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
- Read the entire .xz file into the decoder; never decode partial stream chunks
- Verify expected file size before decode when the source provides one
- Avoid concatenating or padding XZ files ad hoc — use proper multi-stream formats
- Handle downloads transactionally: write to temp, verify, then rename
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
- Invalid XZ stream: truncated block header
- Invalid XZ stream: padding without a stream
- Invalid XZ stream: truncated stream framing
- Invalid XZ stream: footer magic mismatch
- Invalid XZ stream: footer CRC32 mismatch
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f0952f38d6ec28b5.
Report an issue: GitHub.