nexu-io/open-design · error · Error
invalid zip: missing central directory
Error message
invalid zip: missing central directory
What it means
findEndOfCentralDirectory scans backward from the end of the buffer (up to 65557 bytes, the max EOCD + comment) for the End-of-Central-Directory signature 0x06054b50 (EOCD_SIG). If no match is found, the buffer has no recognizable EOCD record and is rejected. Maps to HTTP 400.
Source
Thrown at apps/daemon/src/design/claude-design-import.ts:229
entries.push({
name,
method,
compressedSize,
uncompressedSize,
localOffset,
isDirectory: name.endsWith('/'),
});
offset += 46 + nameLen + extraLen + commentLen;
}
return entries;
}
function findEndOfCentralDirectory(zip: Buffer): number {
const min = Math.max(0, zip.length - 0xffff - 22);
for (let i = zip.length - 22; i >= min; i -= 1) {
if (zip.readUInt32LE(i) === EOCD_SIG) return i;
}
throw new Error('invalid zip: missing central directory');
}
function readEntryBody(zip: Buffer, entry: ZipEntry): Buffer {
const offset = entry.localOffset;
if (zip.readUInt32LE(offset) !== LOCAL_SIG) {
throw new Error(`invalid zip local header: ${entry.name}`);
}
const nameLen = zip.readUInt16LE(offset + 26);
const extraLen = zip.readUInt16LE(offset + 28);
const bodyStart = offset + 30 + nameLen + extraLen;
const bodyEnd = bodyStart + entry.compressedSize;
if (bodyEnd > zip.length) throw new Error(`zip entry exceeds archive: ${entry.name}`);
const compressed = zip.slice(bodyStart, bodyEnd);
if (entry.method === 0) return Buffer.from(compressed);
// A genuinely empty deflate payload would still occupy at least the BFINAL
// marker; an entirely missing payload cannot be inflated, so treat it as
// empty rather than handing a zero-length buffer to zlib.
if (compressed.length === 0) return Buffer.alloc(0);View on GitHub (pinned to 5be4028344)
Solutions
- Confirm the file is genuinely a zip (`file upload.zip` should report 'Zip archive data').
- Re-export from Claude Design and re-upload.
- Check the uploaded file size is non-zero and matches the source.
- If the zip has a very large comment, re-create it without the comment.
Defensive patterns
Strategy: validation
Validate before calling
// Reject non-zip uploads before invoking the importer.
import { execFileSync } from 'node:child_process';
function isZipArchive(file: string): boolean {
try {
const out = execFileSync('file', ['-b', file], { encoding: 'utf8' });
return /Zip archive/i.test(out);
} catch {
return false;
}
}
if (!isZipArchive(uploadPath)) return res.status(400).json({ error: 'expected a real zip' }); Try / catch
try {
await importClaudeDesignZip(zipPath, projectDir);
} catch (err) {
if (String(err).includes('missing central directory')) {
return res.status(400).json({ error: 'file is not a valid zip archive' });
}
throw err;
} Prevention
- Gate uploads with a `file` / magic-byte check, not just the .zip extension.
- Reject zero-byte uploads.
- Confirm the upload completed before persisting.
When it happens
Trigger: importClaudeDesignZip on a buffer that contains no EOCD signature: a non-zip file renamed to .zip, an empty file, a gzip/tar/rar, or a zip whose trailing comment exceeds 0xFFFF bytes and masks the EOCD outside the scan window.
Common situations: Wrong file type uploaded (html/json/png renamed .zip); a truncated upload (EOCD lives at the end); uploading a .gz or .tar by mistake; an empty file.
Related errors
- invalid zip central directory entry
- invalid zip local header: ${entry.name}
- zip contains too many files
- zip file too large: ${relPath}
- zip entry size mismatch: ${relPath}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/71d7807dbd8127e1.
Report an issue: GitHub.