can1357/oh-my-pi · error · ArchiveError

Invalid ZIP archive: truncated central directory

Error message

Invalid ZIP archive: truncated central directory

What it means

Thrown by readCentralDirectoryInfo when the declared number of central-directory entries exceeds what the declared directory size can physically hold (each central header is at least 46 bytes). The EOCD/ZIP64 metadata is internally inconsistent — the directory would have to be truncated to contain that many entries.

Source

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

	let offset = readUInt32LE(tail, eocdIndex + 16);
	if (
		disk !== 0 ||
		centralDisk !== 0 ||
		(entriesOnDisk !== U16_MAX && entries !== U16_MAX && entriesOnDisk !== entries)
	) {
		throw new ArchiveError("Multi-volume ZIP archives are not supported");
	}
	const needsZip64 = entriesOnDisk === U16_MAX || entries === U16_MAX || size === U32_MAX || offset === U32_MAX;
	const zip64 = await readZip64Info(source, tail, tailStart, eocdOffset);
	let physicalEnd = eocdOffset;
	if (zip64) {
		({ entries, size, offset, physicalEnd } = zip64);
	} else if (needsZip64) {
		throw new ArchiveError("Invalid ZIP archive: missing ZIP64 central-directory metadata");
	}
	assertEntryCount(entries, limits);
	assertIndexSize(size, limits, "ZIP central directory");
	if (entries > Math.floor(size / 46)) throw new ArchiveError("Invalid ZIP archive: truncated central directory");
	const declaredOffset = offset;
	const info = { entries, size, offset, physicalEnd, archiveOffset: 0 };
	info.offset = await locateCentralDirectory(source, info);
	info.archiveOffset = info.offset - declaredOffset;
	checkedEnd(info.offset, info.size, source.size, "central directory");
	return info;
}

function filetimeToMs(bytes: Uint8Array, offset: number): number | undefined {
	let value = 0n;
	for (let index = 7; index >= 0; index--) value = (value << 8n) | BigInt(bytes[offset + index]!);
	const milliseconds = value / 10_000n - WINDOWS_EPOCH_FILETIME_MS;
	const number = Number(milliseconds);
	return Number.isSafeInteger(number) ? number : undefined;
}

function parseNtfsMtime(data: Uint8Array): number | undefined {
	if (data.byteLength < 4) return undefined;

View on GitHub (pinned to 9690622007)

Solutions

  1. Test the archive with `unzip -t archive.zip` to confirm external agreement on corruption.
  2. Repair with `zip -FF broken.zip --out fixed.zip`.
  3. Re-download or regenerate the archive from its source files.
  4. Verify integrity at transfer time (checksum the archive after download) so corruption is caught before reading.

Example fix

// before: trusting the transfer implicitly
await download(url, path);
const zip = readZip(path);
// after: verify checksums before parsing
await download(url, path);
const ok = await Bun.password; // pseudo — verify via sha256
if (sha256(await Bun.file(path).arrayBuffer()) !== expectedSha) throw new Error("archive corrupted in transit");
const zip = readZip(path);
Defensive patterns

Strategy: validation

Validate before calling

// Cheap structural sanity check before parsing: EOCD signature in the last 64KB.
const tail = new Uint8Array(await Bun.file(path).slice(-65557).arrayBuffer());
const eo = findLastIndex4LE(tail, 0x06054b50); // scan for PK\x05\x06
if (eo < 0) throw new Error("no EOCD signature; archive is corrupt or not a zip");

Try / catch

try {
  const zip = readZip(path);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("truncated central directory")) {
    // EOCD fields are internally inconsistent -> treat as corrupt input
    await $`zip -FF ${path} --out fixed.zip`.nothrow();
    return readZip("fixed.zip");
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling zip info/read on an archive where the entry count or central-directory size fields were corrupted or written incorrectly, e.g. count > floor(size/46).

Common situations: Bit-level corruption of a large archive (flipped bytes in the EOCD); a buggy writer updating one field but not the other; a truncated transfer that happened to preserve a valid-looking EOCD elsewhere in the tail.

Related errors


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