{"record":{"id":"7973bc5b4692169a","repo":"paperclipai/paperclip","slug":"export-output-directory-root-already-contains-f","errorCode":null,"errorMessage":"Export output directory ${root} already contains files. Re-run interactively, pass --force, or choose an empty directory.","messagePattern":"Export output directory (.+?) already contains files\\. Re-run interactively, pass --force, or choose an empty directory\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/company.ts","lineNumber":1255,"sourceCode":"  opts: { force?: boolean } = {},\n): Promise<void> {\n  const root = path.resolve(outDir);\n  const stats = await stat(root).catch(() => null);\n  if (!stats) return;\n  if (!stats.isDirectory()) {\n    throw new Error(`Export output path ${root} exists and is not a directory.`);\n  }\n\n  const entries = await readdir(root);\n  if (entries.length === 0) return;\n\n  // --force skips the guard for non-interactive/automated callers (e.g. the\n  // nightly backup routine, which exports into a git clone that legitimately\n  // still holds .git and BACKUP-README.md after cleaning tracked content).\n  if (opts.force) return;\n\n  if (!process.stdin.isTTY || !process.stdout.isTTY) {\n    throw new Error(`Export output directory ${root} already contains files. Re-run interactively, pass --force, or choose an empty directory.`);\n  }\n\n  const confirmed = await p.confirm({\n    message: `Overwrite existing files in ${root}?`,\n    initialValue: false,\n  });\n\n  if (p.isCancel(confirmed) || !confirmed) {\n    throw new Error(\"Export cancelled.\");\n  }\n}\n\nfunction matchesPrefix(company: Company, selector: string): boolean {\n  return company.issuePrefix.toUpperCase() === selector.toUpperCase();\n}\n\nexport function resolveCompanyForDeletion(\n  companies: Company[],","sourceCodeStart":1237,"sourceCodeEnd":1273,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/commands/client/company.ts#L1237-L1273","documentation":"Thrown by confirmOverwriteExportDirectory when the output directory already contains files, no --force was passed, and the terminal is non-interactive (stdin or stdout lacks a TTY). The guard prevents silently overwriting unrelated content in automated contexts where no prompt can be shown.","triggerScenarios":"Running an export in cron/CI without --force into a directory that already has files. Piping output, backgrounding, or any non-TTY context. A nightly backup that targets a populated git clone without the --force allowance.","commonSituations":"Nightly backup scripts into a maintained git clone (the comment in code calls this out). Re-running an export after a partial failure left files behind. CI workers reusing a workspace directory.","solutions":["Add --force to the export command for automated runs into a known directory.","Point --out at an empty or fresh directory each run.","Re-run the command in an interactive terminal and accept the overwrite prompt.","For backup-into-git-clone workflows, ensure the routine passes --force (the documented allowance)."],"exampleFix":"# before (cron into populated clone)\n0 3 * * * paperclipai company export --company cmp_abc --out /srv/backup-clone\n# after\n0 3 * * * paperclipai company export --company cmp_abc --out /srv/backup-clone --force","handlingStrategy":"validation","validationCode":"async function ensureExportOutEmptyOrForced(outDir: string, force: boolean): Promise<void> {\n  const fs = await import(\"fs/promises\");\n  const stat = await fs.stat(outDir).catch(() => null);\n  if (!stat || !stat.isDirectory()) return;\n  const entries = await fs.readdir(outDir);\n  if (entries.length > 0 && !force && (!process.stdin.isTTY || !process.stdout.isTTY)) {\n    throw new Error(`Directory ${outDir} not empty and terminal is non-interactive. Pass --force.`);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Standardize on --force for documented backup-into-clone workflows.","Have CI export into a unique tempdir per run.","Detect TTY in wrapper scripts and assert --force is set when non-interactive."],"tags":["cli","filesystem","non-interactive","company-export","validation","tty"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}