{"record":{"id":"692787d42e3d2bc0","repo":"different-ai/openwork","slug":"target-folder-must-be-empty","errorCode":null,"errorMessage":"Target folder must be empty","messagePattern":"Target folder must be empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/workspace-archive.mjs","lineNumber":290,"sourceCode":"    included.push(entry.rel);\n  }\n  files.push({\n    name: \"manifest.json\",\n    data: `${JSON.stringify({\n      version: 1,\n      createdAtMs: nowMs(),\n      workspace: { id: workspace.id, name: workspace.name, path: workspace.path },\n      included,\n      excluded,\n    }, null, 2)}\\n`,\n  });\n  await writeZip(outputPath, files);\n  return { outputPath, included: included.length, excluded };\n}\n\nexport async function importWorkspaceConfig({ archivePath, targetDir, name }) {\n  if (await pathExists(targetDir)) {\n    if (!(await isDirectoryEmpty(targetDir))) throw new Error(\"Target folder must be empty\");\n  }\n  await mkdir(targetDir, { recursive: true });\n\n  const archiveStats = await stat(archivePath);\n  if (archiveStats.size > MAX_ARCHIVE_BYTES) throw new Error(\"Workspace archive is too large.\");\n  const buffer = await readFile(archivePath);\n  for (const entry of listZipEntries(buffer)) {\n    if (entry.name === \"manifest.json\" || entry.name.endsWith(\"/\")) continue;\n    if (!isSafeArchivePath(entry.name)) throw new Error(\"Archive contains an unsafe path\");\n    if (!(entry.name === \"opencode.json\" || entry.name.startsWith(\".opencode/\"))) continue;\n    if (isSecretName(path.basename(entry.name))) continue;\n    const outPath = path.join(targetDir, ...entry.name.split(\"/\"));\n    await mkdir(path.dirname(outPath), { recursive: true });\n    await writeFile(outPath, readZipEntryData(buffer, entry));\n  }\n\n  const opencodeDir = path.join(targetDir, \".opencode\");\n  if (!(await pathExists(opencodeDir))) throw new Error(\"Archive is missing .opencode config\");","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/desktop/electron/workspace-archive.mjs#L272-L308","documentation":"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'.","triggerScenarios":"Calling importWorkspaceConfig with a targetDir that already exists and is non-empty (readdir returns >= 1 entries).","commonSituations":"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.","solutions":["Pass a new, empty (or nonexistent) targetDir for the import","Clear the existing directory (move its contents aside or delete) before importing","Inspect for leftovers from a previous failed import and clean them up"],"exampleFix":"// before\nawait importWorkspaceConfig({ archivePath: 'ws.zip', targetDir: '/ws/existing' })\n// after\nawait importWorkspaceConfig({ archivePath: 'ws.zip', targetDir: '/ws/existing-import' })  // empty or new","handlingStrategy":"validation","validationCode":"import { readdir, stat } from 'node:fs/promises';\nasync function targetIsEmpty(dir) {\n  try { return (await readdir(dir)).length === 0; }\n  catch { return true; } // nonexistent counts as empty\n}","typeGuard":"function isUsableTarget(dirEmpty) {\n  return dirEmpty === true;\n}","tryCatchPattern":"try {\n  await importWorkspaceConfig({ archivePath, targetDir });\n} catch (err) {\n  if (err.message === 'Target folder must be empty') {\n    // ask user to pick an empty folder or clear the existing one\n  } else throw err;\n}","preventionTips":["Default the import target to a fresh folder name","Clean up leftovers from failed imports before retrying","Never point imports at existing project directories"],"tags":["filesystem","validation","import-conflict"],"backgroundTag":"target-directory-not-empty","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}