can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: truncated stream framing

Error message

Invalid XZ stream: truncated stream framing

What it means

After stripping trailing zero padding, discoverStreams() requires at least 24 bytes remaining — the minimum for a 12-byte stream header plus a 12-byte stream footer. A shorter remainder means the stream framing is cut off mid-structure, so the library refuses to parse it rather than produce garbage.

Source

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

	}
	while (cursor.pos < cursor.limit)
		if (bytes[cursor.pos++] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero index padding");
	return records;
}

function discoverStreams(bytes: Uint8Array): XzStream[] {
	if (bytes.byteLength === 0 || (bytes.byteLength & 3) !== 0)
		throw new ArchiveError("Invalid XZ stream: size is not a multiple of four bytes");
	const streams: XzStream[] = [];
	let end = bytes.byteLength;
	while (end > 0) {
		let padding = 0;
		while (end >= 4 && bytes[end - 1] === 0 && bytes[end - 2] === 0 && bytes[end - 3] === 0 && bytes[end - 4] === 0) {
			end -= 4;
			padding += 4;
		}
		if (end === 0) throw new ArchiveError("Invalid XZ stream: padding without a stream");
		if (end < 24) throw new ArchiveError("Invalid XZ stream: truncated stream framing");
		const footerStart = end - 12;
		if (bytes[footerStart + 10] !== 0x59 || bytes[footerStart + 11] !== 0x5a)
			throw new ArchiveError("Invalid XZ stream: footer magic mismatch");
		if (crc32(bytes.subarray(footerStart + 4, footerStart + 10)) !== read32LE(bytes, footerStart))
			throw new ArchiveError("Invalid XZ stream: footer CRC32 mismatch");
		const flag0 = bytes[footerStart + 8]!;
		const flag1 = bytes[footerStart + 9]!;
		if (flag0 !== 0 || (flag1 & 0xf0) !== 0) throw new ArchiveError("Unsupported XZ stream flags");
		const checkId = flag1 & 0x0f;
		checkSize(checkId);
		const indexSize = (read32LE(bytes, footerStart + 4) + 1) * 4;
		if (!Number.isSafeInteger(indexSize) || indexSize > footerStart)
			throw new ArchiveError("Invalid XZ stream: backward index size is invalid");
		const indexStart = footerStart - indexSize;
		const records = parseIndex(bytes, indexStart, indexSize);
		let blocksSize = 0;
		for (const record of records) {
			blocksSize += Math.ceil(record.unpaddedSize / 4) * 4;

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify file size matches the expected .xz size and re-download/re-copy if truncated
  2. Check that the source transfer completed (checksum the file)
  3. If slicing a buffer manually, include at least the full 24-byte header+footer framing
  4. Only strip padding you know belongs between streams; keep whole streams intact

Example fix

// before
const bytes = buf.subarray(0, 20); // truncated
await xzDecode(bytes);
// after
if (buf.byteLength < 24) throw new Error("xz buffer too small (<24 bytes)");
const bytes = buf;
await xzDecode(bytes);
Defensive patterns

Strategy: validation

Validate before calling

if (bytes.byteLength < 24) throw new Error(`xz input too small: ${bytes.byteLength} bytes (min 24)`);

Type guard

null

Try / catch

try {
  await xzDecode(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("truncated stream framing")) {
    throw new Error("XZ file is truncated or incomplete");
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a buffer whose non-padding tail is shorter than 24 bytes — e.g. a truncated .xz download, a partial read, or a slice that ends mid-footer. Also happens when concatenation handling drops the leading stream's header.

Common situations: Interrupted file transfers, HTTP range requests that fetched only part of the file, copying a file while it was still being written, or incorrectly slicing multi-stream xz buffers.

Related errors


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