can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: size changed while extracting '${member

Error message

Invalid CAB archive: size changed while extracting '${memberPath}'

What it means

Thrown by CabMemberSource.read when the size argument passed at extraction time differs from the size the CFFILE entry declared when the archive was indexed. The archive index is built once with fixed per-member sizes; this is an internal consistency check indicating the member source is being driven with a stale or mismatched size.

Source

Thrown at packages/utils/src/ar/cab.ts:227

				);
			}
			output.set(decoded, outputPosition);
			outputPosition += decoded.byteLength;
			position = payloadEnd;
		}
		return output;
	}
}

class CabMemberSource implements MemberSource {
	readonly #folder: CabFolder;
	readonly #offset: number;
	readonly #declaredSize: number;

	constructor(folder: CabFolder, offset: number, size: number) {
		this.#folder = folder;
		this.#offset = offset;
		this.#declaredSize = size;
	}

	async read(size: number, memberPath: string): Promise<Uint8Array> {
		if (size !== this.#declaredSize) {
			throw new ArchiveError(`Invalid CAB archive: size changed while extracting '${memberPath}'`);
		}
		const folder = await this.#folder.readAll();
		const end = this.#offset + size;
		if (!Number.isSafeInteger(end) || this.#offset < 0 || end > folder.byteLength) {
			throw new ArchiveError(`Invalid CAB archive: member '${memberPath}' is outside its folder data`);
		}
		return folder.slice(this.#offset, end);
	}
}

async function readCabArchive(source: ByteSource, options: Parameters<FormatReader>[1]): Promise<ArchiveIndexEntry[]> {
	if (source.size < FIXED_HEADER_SIZE) throw new ArchiveError("Invalid CAB archive: truncated CFHEADER");
	const fixed = await readExact(source, 0, FIXED_HEADER_SIZE);

View on GitHub (pinned to 9690622007)

Solutions

  1. Always pass the entry.size from the same ArchiveIndexEntry that produced the member source
  2. Do not cache ArchiveIndexEntry/member sources across archive reads; re-index per extraction
  3. Check for code that mutates entry.size or passes a computed size instead of the declared one
  4. If your extraction pipeline legitimately knows a different size, re-read the archive so the index matches

Example fix

// before
const size = files[name]; // stale map from a previous run
const data = await entry.storage.source.read(size, name);
// after
const data = await entry.storage.source.read(entry.size, name);
Defensive patterns

Strategy: type-guard

Validate before calling

// before reading, ensure the size comes from the same indexed entry
function canRead(entry: ArchiveIndexEntry, size: number): boolean {
  return entry.storage?.type === 'member' && size === entry.size;
}

Type guard

function isMemberEntry(entry: ArchiveIndexEntry): entry is ArchiveIndexEntry & { storage: { type: 'member'; source: MemberSource } } {
  return entry.storage?.type === 'member';
}

Try / catch

try {
  const data = await entry.storage.source.read(entry.size, entry.path);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('size changed while extracting'))
    throw new Error('Size used for read does not match the indexed entry — re-index the archive.');
  throw err;
}

Prevention

When it happens

Trigger: Calling a member's read/storage path with a size different from the entry.size returned in the ArchiveIndexEntry — e.g. using an entry from a previous index run against a new archive, or hand-modifying the size before reading.

Common situations: Caching ArchiveIndexEntry objects across archive re-reads; passing entry.size to a different member's source; custom extraction code that overrides the declared size.

Related errors


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