nexu-io/open-design · error · Error

zip does not contain an HTML file

Error message

zip does not contain an HTML file

What it means

Thrown by `importClaudeDesignZip` after collecting files if `chooseEntryFile` returns null — meaning no entry path matched `/\.html?$/i`. Claude Design exports are HTML-based and the importer requires at least one `.html` or `.htm` file to select as the entry point.

Source

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

    // 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);
    }
    return pending;
  };

  await mkdir(projectDir, { recursive: true });
  await Promise.all(files.map(async (f) => {
    const target = safeJoin(projectDir, f.path);
    await ensureDir(path.dirname(target));
    await writeFile(target, f.body);
  }));

View on GitHub (pinned to 5be4028344)

Solutions

  1. Ensure the archive includes at least one `.html`/`.htm` file (typically `index.html`).
  2. Re-export from Claude Design so the HTML entry is included.
  3. Confirm you are uploading a Claude Design HTML export and not a different archive type.
Defensive patterns

Strategy: validation

Validate before calling

const fileEntries = entries.filter((e) => !e.isDirectory).map((e) => e.name);
const hasHtml = fileEntries.some((p) => /\.html?$/i.test(p));
if (!hasHtml) {
  throw new Error('archive contains no .html/.htm entry file');
}

Try / catch

try {
  await importClaudeDesignZip(zipPath, projectDir);
} catch (err) {
  if (err instanceof Error && err.message === 'zip does not contain an HTML file') {
    // ensure the archive includes index.html and re-upload
  }
  throw err;
}

Prevention

When it happens

Trigger: An archive that contains files (passes the empty-archive check) but none of them have an `.html` or `.htm` extension — e.g. only `.css`, `.js`, `.png`, or extensionless files.

Common situations: Zipping the wrong directory (assets folder instead of the build output). An export pipeline that emits `index` without the `.html` extension. A non-Claude-Design archive uploaded through the Claude Design import path.

Related errors


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