can1357/oh-my-pi · error · ArchiveError

Invalid ZIP archive: ${what} exceeds archive size

Error message

Invalid ZIP archive: ${what} exceeds archive size

What it means

checkedEnd in zip.ts throws this when start + size computes past the end of the archive (or overflows a safe integer). It protects callers from records that claim data beyond the file, which would otherwise produce out-of-bounds reads or undefined behavior downstream.

Source

Thrown at packages/utils/src/ar/zip.ts:107

}

interface ParsedZipEntry {
	entry: ArchiveIndexEntry;
	isSymlink: boolean;
}

function archiveError(error: unknown, context: string): ArchiveError {
	if (error instanceof ArchiveError) return error;
	return new ArchiveError(`${context}: ${error instanceof Error ? error.message : String(error)}`);
}

function checkedEnd(start: number, size: number, archiveSize: number, what: string): number {
	if (!Number.isSafeInteger(start) || !Number.isSafeInteger(size) || start < 0 || size < 0) {
		throw new ArchiveError(`Invalid ZIP archive: ${what} has an invalid range`);
	}
	const end = start + size;
	if (!Number.isSafeInteger(end) || end > archiveSize) {
		throw new ArchiveError(`Invalid ZIP archive: ${what} exceeds archive size`);
	}
	return end;
}

function findEocd(tail: Uint8Array): number {
	for (let offset = tail.byteLength - EOCD_LENGTH; offset >= 0; offset--) {
		if (readUInt32LE(tail, offset) !== EOCD_SIGNATURE) continue;
		if (offset + EOCD_LENGTH + readUInt16LE(tail, offset + 20) === tail.byteLength) return offset;
	}
	throw new ArchiveError("Invalid ZIP archive: missing end of central directory");
}

async function readZip64Info(
	source: ByteSource,
	tail: Uint8Array,
	tailStart: number,
	eocdOffset: number,
): Promise<CentralDirectoryInfo | undefined> {

View on GitHub (pinned to 9690622007)

Solutions

  1. Run `unzip -t` / re-download the archive; this error almost always means real truncation
  2. If the ZIP came from your own writer, ensure the central directory is written after all entry data
  3. For large archives, confirm the file wasn't cut by a size limit (upload cap, tar slice, git-lfs filter)

Example fix

// before
const zip = await readZip(await Bun.file(upload.path).bytes());
// after
const file = Bun.file(upload.path);
if (file.size < upload.expectedBytes) throw new Error(`zip truncated: ${file.size} < ${upload.expectedBytes}`);
const zip = await readZip(await file.bytes());
Defensive patterns

Strategy: validation

Validate before calling

const file = Bun.file(zipPath);
if (file.size < minimumExpectedBytes) {
  throw new Error(`zip truncated: ${file.size} bytes, expected >= ${minimumExpectedBytes}`);
}
const bytes = await file.bytes();
const dv = new DataView(bytes.buffer);
if (dv.getUint32(bytes.length - 22, true) !== 0x06054b50 && !findEocdSig(bytes)) {
  throw new Error("zip EOCD not at expected location — likely truncated");
}

Try / catch

try {
  const zip = await readZip(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("exceeds archive size")) {
    return { ok: false, reason: "truncated zip" };
  }
  throw err;
}

Prevention

When it happens

Trigger: readZip parsing a central directory entry, local header, or data span whose declared offset+size exceeds source size; typical for truncated files or forged header values.

Common situations: Partially downloaded ZIPs; archives on a disk that filled up mid-write; entries listed in the central directory whose local data was never written.

Related errors


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