nexu-io/open-design · error · Error

zip entry exceeds archive: ${entry.name}

Error message

zip entry exceeds archive: ${entry.name}

What it means

readEntryBody computes bodyEnd = bodyStart + entry.compressedSize and rejects when bodyEnd > zip.length. The central directory declared a compressed size that would read past the end of the archive. Maps to HTTP 400.

Source

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

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);
  // When the central directory advertises 0 (streaming zips with data
  // descriptors), fall back to the per-file ceiling so legitimate non-empty
  // payloads decode instead of being silently truncated. The post-decode
  // checks in the caller enforce MAX_FILE_BYTES and total-bytes limits.
  const cap = entry.uncompressedSize > 0 ? entry.uncompressedSize : MAX_FILE_BYTES;
  return inflateRawSync(compressed, { maxOutputLength: cap });
}

function sanitizeZipPath(name: string): string {
  if (name.includes('\0')) throw new Error('invalid zip file name');
  if (/^[A-Za-z]:/.test(name) || name.startsWith('/')) {
    throw new Error('absolute zip paths are not allowed');

View on GitHub (pinned to 5be4028344)

Solutions

  1. Re-download or re-export the zip fully and verify the byte size matches the source.
  2. Retry the upload over a stable connection.
  3. Run `unzip -t file.zip` locally to confirm integrity before uploading.
Defensive patterns

Strategy: validation

Validate before calling

// Compare received file size to the source and run an integrity test.
import { statSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
function zipIntact(file: string): boolean {
  if (statSync(file).size === 0) return false;
  try { execFileSync('unzip', ['-tqq', file], { stdio: 'ignore' }); return true; }
  catch { return false; }
}

Try / catch

try {
  await importClaudeDesignZip(zipPath, projectDir);
} catch (err) {
  if (String(err).includes('exceeds archive')) {
    return res.status(400).json({ error: 'zip is truncated; re-download fully' });
  }
  throw err;
}

Prevention

When it happens

Trigger: A central-directory entry whose compressedSize field points beyond the archive boundary: a truncated archive, a central directory copied from a larger original, or a data-descriptor/streaming zip whose sizes are inconsistent with the buffer.

Common situations: Partial download of the zip; truncation during multipart upload; archive truncated by a proxy/CDN with a size cap; an interrupted `zip` operation.

Related errors


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