can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: footer magic mismatch

Error message

Invalid XZ stream: footer magic mismatch

What it means

Every XZ stream ends with a 12-byte footer whose last two bytes must be the magic 'YZ' (0x59 0x5A). discoverStreams() checks these bytes after locating the footer; if they differ, the bytes at that position are not an XZ stream footer, so the buffer is not a valid (or is a corrupted) XZ stream.

Source

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

	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;
			if (!Number.isSafeInteger(blocksSize)) throw new ArchiveError("XZ stream uses sizes too large to read safely");
		}
		const start = indexStart - blocksSize - 12;

View on GitHub (pinned to 9690622007)

Solutions

  1. Confirm the input is actually XZ format (check for magic fd 37 7a 58 5a 00 at a stream start)
  2. Re-acquire the file and verify its checksum to rule out corruption
  3. If the data is raw LZMA or another format, use the matching codec instead of xz
  4. If handling concatenated streams, do not insert non-zero filler between them

Example fix

// before
await xzDecode(gzipBytes); // wrong format
// after
import * as gzip from "./gzip";
await gzip.decode(gzipBytes); // use the right codec per format
Defensive patterns

Strategy: validation

Validate before calling

function hasXzMagic(bytes: Uint8Array): boolean {
  return bytes.byteLength >= 6
    && bytes[0] === 0xfd && bytes[1] === 0x37 && bytes[2] === 0x7a
    && bytes[3] === 0x58 && bytes[4] === 0x5a && bytes[5] === 0x00;
}

Type guard

null

Try / catch

try {
  await xzDecode(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("footer magic mismatch")) {
    throw new Error("Input is not a valid XZ stream — check file format");
  }
  throw err;
}

Prevention

When it happens

Trigger: Feeding a non-XZ archive (gzip, zip, tar, raw LZMA) to the xz decoder; a footer corrupted by a bad transfer or write; an off-by-N slice that shifts the footer position so the magic bytes land elsewhere.

Common situations: Opening a file with the wrong extension, hand-editing or post-processing an .xz file, concatenating streams with junk between them, or byte-level corruption from a faulty disk/transfer.

Related errors


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