nexu-io/open-design · error · Error

zip contains too many files

Error message

zip contains too many files

What it means

Thrown by `importClaudeDesignZip` when the number of decoded non-directory entries reaches `MAX_FILES` (5000). The check runs at the top of the per-entry loop before adding the next file, capping the total entry count to protect the daemon from zip bombs by file count.

Solutions

  1. Reduce the archive to fewer than 5000 file entries by removing unnecessary files (e.g. `node_modules`, build output).
  2. If exporting from a tool, configure it to emit only the HTML and required assets.
  3. Reject the upload at the request layer if the declared entry count exceeds 5000.
Defensive patterns

Strategy: validation

Validate before calling

const MAX_FILES = 5000;

async function zipEntryCountDoesNotExceed(zipPath: string): Promise<boolean> {
  const zip = await readFile(zipPath);
  const entries = readCentralDirectory(zip); // reuse the importer's reader
  return entries.filter((e) => !e.isDirectory).length <= MAX_FILES;
}

if (!await zipEntryCountDoesNotExceed(zipPath)) {
  throw new Error('zip exceeds the 5000-file cap');
}

Try / catch

try {
  await importClaudeDesignZip(zipPath, projectDir);
} catch (err) {
  if (err instanceof Error && err.message === 'zip contains too many files') {
    // trim the archive and re-upload
  }
  throw err;
}

Prevention

When it happens

Trigger: Uploading a Claude Design zip archive that declares 5000 or more file entries. A zip bomb or a maliciously crafted archive intended to exhaust file handles or disk.

Common situations: A legitimate but very large export (rare for Claude Design HTML exports). A malicious archive. An archive that includes `node_modules` or other bulky directory trees by accident.

Related errors


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

Appendix: source

Thrown at apps/daemon/src/design/claude-design-import.ts:33

  name: string;
  method: number;
  compressedSize: number;
  uncompressedSize: number;
  localOffset: number;
  isDirectory: boolean;
};

type ImportedFile = { path: string; body: Buffer };

export async function importClaudeDesignZip(zipPath: string, projectDir: string) {
  const zip = await readFile(zipPath);
  const entries = readCentralDirectory(zip);
  const files: ImportedFile[] = [];
  let totalBytes = 0;

  for (const entry of entries) {
    if (entry.isDirectory) continue;
    if (files.length >= MAX_FILES) throw new Error('zip contains too many files');
    const relPath = sanitizeZipPath(entry.name);
    if (entry.uncompressedSize > MAX_FILE_BYTES) {
      throw new Error(`zip file too large: ${relPath}`);
    }

    // Decode first; the central directory's uncompressedSize is unreliable for
    // streaming/data-descriptor zips (it can read 0 even when the payload
    // carries real data). The inflate cap and the post-decode size checks below
    // are authoritative.
    const body = readEntryBody(zip, entry);
    if (body.length > MAX_FILE_BYTES) {
      throw new Error(`zip file too large: ${relPath}`);
    }
    if (entry.uncompressedSize > 0 && body.length !== entry.uncompressedSize) {
      throw new Error(`zip entry size mismatch: ${relPath}`);
    }
    totalBytes += body.length;
    if (totalBytes > MAX_TOTAL_BYTES) throw new Error('zip is too large');

View on GitHub (pinned to 5be4028344)