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
- Test the archive with `unzip -t archive.zip` to confirm external agreement on corruption.
- Repair with `zip -FF broken.zip --out fixed.zip`.
- Re-download or regenerate the archive from its source files.
- 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
- Verify archive checksums after any transfer to catch bit corruption.
- Treat repeated structural-zip failures from one producer as a writer bug and switch tools.
- Don't hand-edit zip binary fields; always re-zip via a standard tool.
- Keep a repair step (`zip -FF`) in ingestion pipelines for untrusted archives.
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
- Invalid ZIP archive: central directory is out of bounds or m
- Invalid ZIP archive: missing ZIP64 central-directory metadat
- Invalid ZIP archive: malformed NTFS extra field
- Invalid ZIP archive: truncated extra-field header
- Invalid ZIP archive: malformed extra field
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/4265d1aff9e0d016.
Report an issue: GitHub.