can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: non-canonical variable-length integer

Error message

Invalid XZ stream: non-canonical variable-length integer

What it means

XZ varints must be canonically encoded: continuation padding bytes after the final (non-continuation) byte must be zero. A zero data byte in a non-final position means either non-canonical encoding (rejected by the spec) or corrupt data.

Source

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

function equalBytes(left: Uint8Array, right: Uint8Array): boolean {
	if (left.byteLength !== right.byteLength) return false;
	for (let index = 0; index < left.byteLength; index++) if (left[index] !== right[index]) return false;
	return true;
}

interface Cursor {
	bytes: Uint8Array;
	pos: number;
	limit: number;
}

function readVarInt(cursor: Cursor): number {
	let value = 0;
	for (let index = 0; index < 9; index++) {
		if (cursor.pos >= cursor.limit) throw new ArchiveError("Invalid XZ stream: truncated variable-length integer");
		const byte = cursor.bytes[cursor.pos++]!;
		if (index > 0 && byte === 0) throw new ArchiveError("Invalid XZ stream: non-canonical variable-length integer");
		value += (byte & 0x7f) * 2 ** (index * 7);
		if (!Number.isSafeInteger(value)) throw new ArchiveError("XZ stream uses sizes too large to read safely");
		if ((byte & 0x80) === 0) return value;
	}
	throw new ArchiveError("Invalid XZ stream: variable-length integer is too long");
}

interface XzRecord {
	unpaddedSize: number;
	uncompressedSize: number;
}

interface XzStream {
	start: number;
	indexStart: number;
	footerStart: number;
	checkId: number;
	records: XzRecord[];

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download or re-compress the .xz file with a standard tool (xz, liblzma).
  2. Locate corruption with `xz -t file.xz` outside the library.
  3. Treat as invalid input; the library intentionally rejects non-canonical encodings per the XZ format spec.
Defensive patterns

Strategy: validation

Validate before calling

// reject obviously corrupt varint prefixes before parsing
function varintRegionLooksSane(bytes, offset, limit) {
  return offset >= 0 && offset <= limit && limit <= bytes.byteLength;
}

Try / catch

try {
  const idx = parseIndex(bytes, indexOffset);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("non-canonical")) {
    throw new Error("XZ stream contains non-canonical/corrupt size fields");
  }
  throw err;
}

Prevention

When it happens

Trigger: readVarInt reads a byte === 0 at index > 0 — i.e. a 0x00 where a meaningful payload or 0x80-continuation byte was required.

Common situations: Corrupted .xz stream, hand-crafted or fuzzed varints, zero-fill overwriting part of an index/block header.

Related errors


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