paperclipai/paperclip · error · Error
Export output directory ${root} already contains files. Re-r
Error message
Export output directory ${root} already contains files. Re-run interactively, pass --force, or choose an empty directory. What it means
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.
Source
Thrown at cli/src/commands/client/company.ts:1255
opts: { force?: boolean } = {},
): Promise<void> {
const root = path.resolve(outDir);
const stats = await stat(root).catch(() => null);
if (!stats) return;
if (!stats.isDirectory()) {
throw new Error(`Export output path ${root} exists and is not a directory.`);
}
const entries = await readdir(root);
if (entries.length === 0) return;
// --force skips the guard for non-interactive/automated callers (e.g. the
// nightly backup routine, which exports into a git clone that legitimately
// still holds .git and BACKUP-README.md after cleaning tracked content).
if (opts.force) return;
if (!process.stdin.isTTY || !process.stdout.isTTY) {
throw new Error(`Export output directory ${root} already contains files. Re-run interactively, pass --force, or choose an empty directory.`);
}
const confirmed = await p.confirm({
message: `Overwrite existing files in ${root}?`,
initialValue: false,
});
if (p.isCancel(confirmed) || !confirmed) {
throw new Error("Export cancelled.");
}
}
function matchesPrefix(company: Company, selector: string): boolean {
return company.issuePrefix.toUpperCase() === selector.toUpperCase();
}
export function resolveCompanyForDeletion(
companies: Company[],View on GitHub (pinned to 67001ec6eb)
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).
Example fix
# before (cron into populated clone) 0 3 * * * paperclipai company export --company cmp_abc --out /srv/backup-clone # after 0 3 * * * paperclipai company export --company cmp_abc --out /srv/backup-clone --force
Defensive patterns
Strategy: validation
Validate before calling
async function ensureExportOutEmptyOrForced(outDir: string, force: boolean): Promise<void> {
const fs = await import("fs/promises");
const stat = await fs.stat(outDir).catch(() => null);
if (!stat || !stat.isDirectory()) return;
const entries = await fs.readdir(outDir);
if (entries.length > 0 && !force && (!process.stdin.isTTY || !process.stdout.isTTY)) {
throw new Error(`Directory ${outDir} not empty and terminal is non-interactive. Pass --force.`);
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Applying a company import from a non-interactive terminal re
- Export output path ${root} exists and is not a directory.
- Applying a company import with --json requires --yes. Use --
- Refusing to write export file outside output directory: ${re
- `paperclipai connect` is interactive. For scripts, pass --ap
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/7973bc5b4692169a.
Report an issue: GitHub.