can1357/oh-my-pi · error · ArchiveError
Invalid XZ stream: truncated block data
Error message
Invalid XZ stream: truncated block data
What it means
The decoder computed where the block's integrity check should live (compressedEnd + 4-byte alignment padding) and it extends past the end of the input buffer. The stream is physically truncated — the bytes the index promised are not there.
Source
Thrown at packages/utils/src/ar/codecs/xz.ts:465
throw new ArchiveError("Invalid XZ stream: truncated filter properties");
filters.push({ id, properties: bytes.slice(cursor.pos, cursor.pos + propertySize) });
cursor.pos += propertySize;
}
while (cursor.pos < cursor.limit)
if (bytes[cursor.pos++] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero block header padding");
const integritySize = checkSize(checkId);
const compressedSize = record.unpaddedSize - headerSize - integritySize;
if (!Number.isSafeInteger(compressedSize) || compressedSize <= 0)
throw new ArchiveError("Invalid XZ stream: compressed block size is invalid");
if (declaredCompressed !== undefined && declaredCompressed !== compressedSize)
throw new ArchiveError("Invalid XZ stream: block compressed size mismatch");
if (declaredUncompressed !== undefined && declaredUncompressed !== record.uncompressedSize)
throw new ArchiveError("Invalid XZ stream: block uncompressed size mismatch");
const compressedStart = offset + headerSize;
const compressedEnd = compressedStart + compressedSize;
const paddingSize = (4 - ((headerSize + compressedSize) & 3)) & 3;
const checkStart = compressedEnd + paddingSize;
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");View on GitHub (pinned to 9690622007)
Solutions
- Re-download the complete file and compare its size/checksum against the source.
- Check the code path that reads the file — ensure you read the whole file (Bun.file().bytes()/arrayBuffer()) without a byte cap.
- If the archive is being written concurrently, wait for the writer to finish before decompressing.
- Catch ArchiveError and surface a 'file truncated/incomplete' message to users.
Example fix
// before const buf = new Uint8Array(await file.arrayBuffer(), 0, 1_000_000); // capped slice await xzDecompress(buf, maxOutput); // after const buf = await file.bytes(); // full contents await xzDecompress(buf, maxOutput);
Defensive patterns
Strategy: validation
Validate before calling
const stat = await fs.stat(path);
if (expectedSize !== undefined && stat.size < expectedSize) {
throw new Error(`Archive incomplete: ${stat.size}/${expectedSize} bytes`);
}
const bytes = await Bun.file(path).bytes();
if (!isXz(bytes)) throw new Error('Not an XZ stream'); Try / catch
try {
return await xzDecompress(bytes, maxOutput);
} catch (err) {
if (err instanceof ArchiveError && /truncated/i.test(err.message)) {
throw new Error(`Download incomplete — re-fetch ${path}`);
}
throw err;
} Prevention
- Compare file size against the published size before extraction.
- Retry interrupted downloads until the checksum matches.
- Do not decompress archives while a writer is still producing them.
- Read files fully via Bun.file().bytes() instead of capped slices.
When it happens
Trigger: xzDecompress is given a buffer where checkStart + integritySize exceeds bytes.byteLength for some block — typically a partially downloaded or prematurely truncated .xz file.
Common situations: Interrupted downloads, files copied with size limits (e.g. partial rsync/scp), reading a growing log archive mid-write, or a truncated upload.
Related errors
- Invalid XZ stream: truncated filter properties
- Invalid XZ stream: non-canonical variable-length integer
- Invalid XZ stream: truncated stream framing
- Invalid XZ stream: missing block header
- Invalid XZ stream: truncated block header
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/0010c36718e4ccd6.
Report an issue: GitHub.