nexu-io/open-design · error · Error

zip entry size mismatch: ${relPath}

Error message

zip entry size mismatch: ${relPath}

What it means

Thrown by `importClaudeDesignZip` when an entry's central-directory-declared `uncompressedSize` is greater than 0 but does not equal the actual decoded body length. This catches truncated, corrupt, or tampered entries where the declared size and the real payload disagree.

Source

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

  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');

    files.push({ path: relPath, body: normalizeImportedClaudeDesignFile(relPath, body) });
  }

  if (files.length === 0) throw new Error('zip contains no files');
  const entryFile = chooseEntryFile(files.map((f) => f.path));
  if (!entryFile) throw new Error('zip does not contain an HTML file');

  const dirCreates = new Map<string, Promise<string | undefined>>();
  const ensureDir = (dir: string) => {
    let pending = dirCreates.get(dir);
    if (!pending) {
      pending = mkdir(dir, { recursive: true });
      dirCreates.set(dir, pending);
    }

View on GitHub (pinned to 5be4028344)

Solutions

  1. Re-obtain the zip from the original source (re-export or re-download) to rule out truncation/corruption.
  2. Verify the archive with a standard unzip tool; if it reports an error, the archive is corrupt.
  3. If you produce the archive, ensure the writer emits correct uncompressed sizes.
Defensive patterns

Strategy: validation

Validate before calling

for (const entry of entries.filter((e) => !e.isDirectory && e.uncompressedSize > 0)) {
  const body = readEntryBody(zip, entry);
  if (body.length !== entry.uncompressedSize) {
    throw new Error(`entry "${entry.name}" decoded size ${body.length} != declared ${entry.uncompressedSize}`);
  }
}

Try / catch

try {
  await importClaudeDesignZip(zipPath, projectDir);
} catch (err) {
  if (err instanceof Error && /^zip entry size mismatch:/.test(err.message)) {
    // re-obtain the archive; it is corrupt or truncated
  }
  throw err;
}

Prevention

When it happens

Trigger: A zip entry whose declared uncompressed size is, say, 1024 bytes but whose decoded body is a different length. Causes include a truncated download, a corrupt archive, a tampered central directory, or a zip produced by a buggy writer.

Common situations: The archive was partially downloaded or transferred. Disk corruption. A zip writer bug. An attacker altering the central directory to disguise a large payload.

Related errors


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