Skip to content

Import Claude Design ZIP silently fails: RangeError on 0-byte entries (Node 24) and MAX_FILES=500 too low #590

Description

@gabrielvaz

Summary

POST /api/import/claude-design (the Import Claude Design ZIP button in the New Project panel) silently fails for valid .zip files in two distinct ways:

  1. Crash on zero-byte entries (Node 24). inflateRawSync(compressed, { maxOutputLength: 0 }) throws RangeError [ERR_OUT_OF_RANGE] on Node 24 because maxOutputLength must be >= 1. Any entry whose central-directory uncompressedSize is 0 (empty files, common when the zip uses data descriptors — e.g. macOS Archive Utility, streaming zips) breaks the whole import.
  2. MAX_FILES = 500 rejects medium-sized exports. Design-system / prototype exports commonly exceed 500 files (the test asset that surfaced this is 561 files / ~4.4 MB). The existing MAX_TOTAL_BYTES = 100 MB and MAX_FILE_BYTES = 25 MB guards already cover pathological inputs.

The web UI surfaces neither error: apps/web/src/state/projects.ts returns null on !resp.ok, and apps/web/src/App.tsx's handleImportClaudeDesign does if (!result) return;. After picking a file, the button appears to "do nothing".

Reproduction

  1. Build (or use an existing) zip with either:
    • any entry whose central-directory uncompressedSize is 0, or
    • more than 500 entries.
  2. In the running app, click Import Claude Design ZIP in the New Project panel and pick the file.
  3. UI shows nothing.
  4. From a terminal, the underlying daemon error is visible:
    curl -i -F "file=@./your.zip" http://127.0.0.1:<daemon-port>/api/import/claude-design
    HTTP/1.1 400 Bad Request
    {"error":"RangeError [ERR_OUT_OF_RANGE]: The value of \"options.maxOutputLength\" is out of range. It must be >= 1 and <= 9007199254740991. Received 0"}
    
    or
    {"error":"Error: zip contains too many files"}
    

Root cause

apps/daemon/src/claude-design-import.ts:

// L11
const MAX_FILES = 500;

// L104-117 readEntryBody (...):
//   compressed = zip.slice(bodyStart, bodyEnd)  // bodyEnd-bodyStart === entry.compressedSize
//   ...
return inflateRawSync(compressed, { maxOutputLength: entry.uncompressedSize });

Node 24's zlib option validator now requires maxOutputLength >= 1, so the value 0 (legal in earlier versions) throws synchronously.

Suggested fix

Two-line patch in apps/daemon/src/claude-design-import.ts:

-const MAX_FILES = 500;
+const MAX_FILES = 5000;
@@
-  if (entry.method === 0) return Buffer.from(compressed);
-  return inflateRawSync(compressed, { maxOutputLength: entry.uncompressedSize });
+  if (entry.method === 0) return Buffer.from(compressed);
+  if (entry.uncompressedSize === 0) return Buffer.alloc(0);
+  return inflateRawSync(compressed, { maxOutputLength: entry.uncompressedSize });

Bytes-per-file and total-bytes guards already cap risk; MAX_FILES = 5000 is closer to what real design-system / prototype exports look like.

Related UX gap (separate but worth fixing together)

The web layer should surface the daemon's error field instead of swallowing it:

apps/web/src/state/projects.ts (importClaudeDesignZip): return the parsed JSON (including error) instead of null on !resp.ok, and have apps/web/src/App.tsx's handleImportClaudeDesign push the message into a toast. Without this, any future failure in the importer remains invisible to the user.

Environment

  • Node v24.15.0
  • pnpm 10.33.2
  • macOS (darwin-arm64)
  • Repo HEAD: c69dee7 on main

Test asset

Reproduced consistently with a 4.4 MB / 561-file zip containing 74 HTML files at preview/**/*.html, plus one zero-byte docs/components/chip-removable.md. Both failure modes were confirmed: the zero-byte entry triggers (1), and even after patching (1), (2) blocks the import. Both fixes together let the import succeed end-to-end.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions