nexu-io/open-design · error · Error
invalid zip local header: ${entry.name}
Error message
invalid zip local header: ${entry.name} What it means
For each entry, readEntryBody reads the local file header at entry.localOffset (taken from the central directory). The first 4 bytes must be the local file header signature 0x04034b50 (LOCAL_SIG). A mismatch means the central directory's local-header offset is wrong or the local region is corrupt. Maps to HTTP 400.
Source
Thrown at apps/daemon/src/design/claude-design-import.ts:235
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);
// 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 });View on GitHub (pinned to 5be4028344)
Solutions
- Re-export fresh from Claude Design.
- Re-zip with a standard conformant tool (Info-ZIP, Finder, Windows Explorer).
- Avoid self-extracting wrappers; upload a plain deflate zip.
Defensive patterns
Strategy: try-catch
Validate before calling
// Local-header/offset drift isn't cheap to detect without a full parser;
// rely on `unzip -t` as the pre-flight.
import { execFileSync } from 'node:child_process';
function zipPassesTest(file: string): boolean {
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('invalid zip local header')) {
return res.status(400).json({ error: 'zip has inconsistent local headers; re-create it' });
}
throw err;
} Prevention
- Avoid self-extracting archives (prepended stubs shift offsets).
- Do not repair zips with `zip -FF` and then import; re-create cleanly.
- Run `unzip -t` before upload to catch offset drift.
When it happens
Trigger: A zip where the central directory points at offsets that don't hold local headers — typically from a packager that wrote inconsistent offsets, a zip edited after creation, or bytes prepended (e.g. a self-extracting stub) that shifted local headers without adjusting offsets.
Common situations: Self-extracting archives with a prepended stub; zips modified by tools that rewrite the central directory without fixing offsets; archives repaired by 'zip -FF' that left offset drift.
Related errors
- invalid zip central directory entry
- invalid zip: missing central directory
- zip contains too many files
- zip file too large: ${relPath}
- zip entry size mismatch: ${relPath}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/8eec30b809b5532c.
Report an issue: GitHub.