can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: truncated variable-length integer

Error message

Invalid XZ stream: truncated variable-length integer

What it means

XZ variable-length integers (multibyte, up to 9 bytes) encode sizes in the block header and index. readVarInt throws when the cursor reaches its limit before encountering a byte with the continuation bit clear — the integer is cut off by the end of the valid region.

Source

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

	bytes[offset + 3] = value & 0xff;
}

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;

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download the archive; a truncated tail cannot be recovered for indexed streams.
  2. Check that the index region passed to parseIndex matches the stream's actual backward size.
  3. Validate the file against checksums before parsing.
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the index region fits: last 12 bytes are footer, index backward size is in it
if (bytes.byteLength < 12) throw new Error("XZ stream too short to contain an index");

Try / catch

try {
  const idx = parseIndex(bytes, indexOffset);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("truncated variable-length")) {
    throw new Error("XZ index is truncated — file incomplete");
  }
  throw err;
}

Prevention

When it happens

Trigger: Parsing index records (count, unpaddedSize, uncompressedSize) or block header fields (declaredCompressed, declaredUncompressed, filter id) where the cursor limit is hit mid-integer.

Common situations: Truncated .xz file cut off mid-index or mid-block-header; wrong limit passed to the index parser so the region ends inside a varint.

Related errors


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