paperclipai/paperclip · error · Error
Export output path ${root} exists and is not a directory.
Error message
Export output path ${root} exists and is not a directory. What it means
Thrown by confirmOverwriteExportDirectory when the resolved output path already exists as a file (or any non-directory entry) rather than a directory. The exporter needs to mkdir and write files under the root; a file blocking that path is a hard stop.
Source
Thrown at cli/src/commands/client/company.ts:1243
export function resolveExportOutputPath(root: string, relativePath: string): string {
const resolvedRoot = path.resolve(root);
const filePath = path.resolve(resolvedRoot, relativePath);
const rootPrefix = resolvedRoot.endsWith(path.sep) ? resolvedRoot : `${resolvedRoot}${path.sep}`;
if (filePath !== resolvedRoot && !filePath.startsWith(rootPrefix)) {
throw new Error(`Refusing to write export file outside output directory: ${relativePath}`);
}
return filePath;
}
export async function confirmOverwriteExportDirectory(
outDir: string,
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,
});View on GitHub (pinned to 67001ec6eb)
Solutions
- Choose a different --out path that is or will be a directory.
- Remove or rename the blocking file: 'rm <path>' (after confirming it is safe to delete).
- If the path is a symlink to a file, repoint or remove the symlink.
- Use a fresh directory name like 'export-<timestamp>'.
Example fix
# before paperclipai company export --company cmp_abc --out backup.zip # backup.zip is a file # after paperclipai company export --company cmp_abc --out backup-dir/
Defensive patterns
Strategy: validation
Validate before calling
async function ensureExportOutIsDir(outDir: string): Promise<void> {
const fs = await import("fs/promises");
const stat = await fs.stat(outDir).catch(() => null);
if (stat && !stat.isDirectory()) {
throw new Error(`Refusing export: ${outDir} is a file. Choose a directory path.`);
}
} Prevention
- Use directory-style output names (trailing slash convention) in scripts.
- Avoid reusing filenames previously used for zip outputs.
- Check the path with 'ls -ld' before scheduling exports.
When it happens
Trigger: Passing --out pointing at an existing regular file (e.g. a previous export zip, a README, a socket). Pointing --out at a path where a symlink resolves to a file. Reusing a name that was a file output from another tool.
Common situations: User previously ran 'paperclipai company export --out backup.zip' (file) then tries '--out backup.zip' for a folder export. A symlink in the path resolves to a file. Conflicting output naming conventions between scripts.
Related errors
- Export output directory ${root} already contains files. Re-r
- Refusing to write export file outside output directory: ${re
- Worktree config already exists at ${paths.configPath} or ins
- Could not locate local Paperclip skills directory. Expected
- --payload must be a JSON object
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/a00e7a244e192657.
Report an issue: GitHub.