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

  1. Confirm the file is genuinely a zip (`file upload.zip` should report 'Zip archive data').
  2. Re-export from Claude Design and re-upload.
  3. Check the uploaded file size is non-zero and matches the source.
  4. 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

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


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/71d7807dbd8127e1. Report an issue: GitHub.