different-ai/openwork · error

Target folder must be empty

Error message

Target folder must be empty

What it means

importWorkspaceConfig extracts an exported archive into targetDir, which must be empty (or nonexistent) to avoid clobbering an existing workspace. If targetDir exists and contains any entries, it throws 'Target folder must be empty'.

Source

Thrown at apps/desktop/electron/workspace-archive.mjs:290

    included.push(entry.rel);
  }
  files.push({
    name: "manifest.json",
    data: `${JSON.stringify({
      version: 1,
      createdAtMs: nowMs(),
      workspace: { id: workspace.id, name: workspace.name, path: workspace.path },
      included,
      excluded,
    }, null, 2)}\n`,
  });
  await writeZip(outputPath, files);
  return { outputPath, included: included.length, excluded };
}

export async function importWorkspaceConfig({ archivePath, targetDir, name }) {
  if (await pathExists(targetDir)) {
    if (!(await isDirectoryEmpty(targetDir))) throw new Error("Target folder must be empty");
  }
  await mkdir(targetDir, { recursive: true });

  const archiveStats = await stat(archivePath);
  if (archiveStats.size > MAX_ARCHIVE_BYTES) throw new Error("Workspace archive is too large.");
  const buffer = await readFile(archivePath);
  for (const entry of listZipEntries(buffer)) {
    if (entry.name === "manifest.json" || entry.name.endsWith("/")) continue;
    if (!isSafeArchivePath(entry.name)) throw new Error("Archive contains an unsafe path");
    if (!(entry.name === "opencode.json" || entry.name.startsWith(".opencode/"))) continue;
    if (isSecretName(path.basename(entry.name))) continue;
    const outPath = path.join(targetDir, ...entry.name.split("/"));
    await mkdir(path.dirname(outPath), { recursive: true });
    await writeFile(outPath, readZipEntryData(buffer, entry));
  }

  const opencodeDir = path.join(targetDir, ".opencode");
  if (!(await pathExists(opencodeDir))) throw new Error("Archive is missing .opencode config");

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Pass a new, empty (or nonexistent) targetDir for the import
  2. Clear the existing directory (move its contents aside or delete) before importing
  3. Inspect for leftovers from a previous failed import and clean them up

Example fix

// before
await importWorkspaceConfig({ archivePath: 'ws.zip', targetDir: '/ws/existing' })
// after
await importWorkspaceConfig({ archivePath: 'ws.zip', targetDir: '/ws/existing-import' })  // empty or new
Defensive patterns

Strategy: validation

Validate before calling

import { readdir, stat } from 'node:fs/promises';
async function targetIsEmpty(dir) {
  try { return (await readdir(dir)).length === 0; }
  catch { return true; } // nonexistent counts as empty
}

Type guard

function isUsableTarget(dirEmpty) {
  return dirEmpty === true;
}

Try / catch

try {
  await importWorkspaceConfig({ archivePath, targetDir });
} catch (err) {
  if (err.message === 'Target folder must be empty') {
    // ask user to pick an empty folder or clear the existing one
  } else throw err;
}

Prevention

When it happens

Trigger: Calling importWorkspaceConfig with a targetDir that already exists and is non-empty (readdir returns >= 1 entries).

Common situations: Importing twice into the same folder; choosing an existing project directory as the import target; a previous failed import left partial files behind making the dir non-empty.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/692787d42e3d2bc0. Report an issue: GitHub.