can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: header and footer flags differ

Error message

Invalid XZ stream: header and footer flags differ

What it means

The XZ spec requires the stream header flags and stream footer flags to be identical. discoverStreams() compares the header's flag bytes (start+6, start+7) against those read from the footer; a mismatch means the header and footer belong to different streams or one of them was modified, so the archive is internally inconsistent.

Source

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

		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;
		if (start < 0 || start + 12 > bytes.byteLength || !equalBytes(bytes.subarray(start, start + 6), XZ_MAGIC)) {
			throw new ArchiveError("Invalid XZ stream: header position or magic is invalid");
		}
		if (bytes[start + 6] !== flag0 || bytes[start + 7] !== flag1)
			throw new ArchiveError("Invalid XZ stream: header and footer flags differ");
		if (crc32(bytes.subarray(start + 6, start + 8)) !== read32LE(bytes, start + 8))
			throw new ArchiveError("Invalid XZ stream: header CRC32 mismatch");
		streams.unshift({ start, indexStart, footerStart, checkId, records });
		end = start;
		void padding;
	}
	return streams;
}

interface XzFilter {
	id: number;
	properties: Uint8Array;
}

function deltaDecode(bytes: Uint8Array, distance: number): void {
	const history = new Uint8Array(256);
	let position = 0;
	for (let index = 0; index < bytes.byteLength; index++) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-compress the data to regenerate a consistent header/footer pair
  2. Verify with xz -t and restore from backup/checksum if the file is damaged
  3. Never edit stream flags post-compression; change check type only via encoder options (xz --check=...)
  4. When concatenating streams, keep each stream's header/index/footer intact as a unit

Example fix

// before
header[6] = 0x01; // patched check type after the fact
await xzDecode(bytes); // header/footer flags differ
// after
// re-encode: $ xz --check=crc32 --format=xz -c in > out.xz
const bytes = new Uint8Array(await Bun.file("out.xz").arrayBuffer());
await xzDecode(bytes);
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  await xzDecode(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("header and footer flags differ")) {
    throw new Error("XZ header/footer inconsistent — re-compress the archive");
  }
  throw err;
}

Prevention

When it happens

Trigger: Splicing a footer from one .xz onto blocks/index/header of another; editing check-type flags after compression without regenerating both; corruption touching only one of the two flag locations.

Common situations: Manual archive surgery/patching tools, merge scripts that concatenate partial streams, or bit-rot affecting a single byte.

Related errors


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