can1357/oh-my-pi · error · Error
Security output directory is not empty; enable…
Error message
Security output directory is not empty; enable archiveExisting or choose another directory
What it means
normalizeOutput refuses to proceed when the output directory already exists, is a real directory, but is non-empty and archiveExisting was not enabled. Existing content would be clobbered or mixed with new scan output, so the caller must explicitly opt into archiving the old contents or pick an empty directory.
Solutions
- Enable archiveExisting: true in the security output options so prior contents are renamed aside (`.archive-<suffix>`).
- Empty the directory manually (`rm -rf <path>/*`) and re-run.
- Choose a fresh outputRoot path for the new run.
Example fix
// before
const plan = await output({ outputRoot, repositoryRoot });
// after
const plan = await output({ outputRoot, repositoryRoot, archiveExisting: true }); Defensive patterns
Strategy: validation
Validate before calling
import * as fs from "node:fs/promises";
try {
const entries = await fs.readdir(outputPath);
if (entries.length > 0 && !archiveExisting) {
throw new Error(`Output dir not empty (${entries.length} entries); set archiveExisting or clean it`);
}
} catch (e) {
if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
} Try / catch
try {
await output(options);
} catch (err) {
if ((err as Error).message.includes("is not empty")) {
options.archiveExisting = true;
return output(options); // retry, archiving old contents
}
throw err;
} Prevention
- Set archiveExisting: true when reusing an output directory across runs.
- Use a timestamped or per-run output directory name.
- Check readdir length before invoking with default options.
When it happens
Trigger: Calling output()/normalizeOutput with archiveExisting=false (default) where readdir(canonicalCandidate) returns one or more entries.
Common situations: Re-running a scan against a previously used output dir; user pointed outputRoot at a general-purpose directory like ~/tmp or the project root's parent; leftover files from another tool.
Related errors
- Security knowledge base is not a file
- Security output directory must be outside the scanned…
- Shared-folder destination escapes its configured root
- Absolute paths are not allowed in
- Cannot write unsafe ZIP member path
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/03099200c5e62adc.
Report an issue: GitHub.
Appendix: source
Thrown at packages/coding-agent/src/security/preflight.ts:263
archiveExisting: boolean,
): Promise<SecurityOutputPlan> {
const requested = path.resolve(outputRoot);
const parent = await fs.realpath(path.dirname(requested));
const canonicalCandidate = path.join(parent, path.basename(requested));
if (pathIsWithin(canonicalCandidate, repositoryRoot)) {
throw new Error("Security output directory must be outside the scanned repository");
}
let existingState: SecurityOutputPlan["existingState"] = "absent";
try {
const stats = await fs.lstat(canonicalCandidate);
if (stats.isSymbolicLink()) throw new Error("Security output directory must not be a symbolic link");
if (!stats.isDirectory()) throw new Error("Security output path exists and is not a directory");
const real = await fs.realpath(canonicalCandidate);
if (real !== canonicalCandidate) throw new Error("Security output directory does not have a canonical identity");
const entries = await fs.readdir(canonicalCandidate);
existingState = entries.length === 0 ? "empty" : "archivable";
if (entries.length > 0 && !archiveExisting) {
throw new Error("Security output directory is not empty; enable archiveExisting or choose another directory");
}
} catch (error) {
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
await fs.mkdir(canonicalCandidate, { recursive: false, mode: 0o700 });
existingState = "empty";
}
if (process.platform !== "win32") await fs.chmod(canonicalCandidate, 0o700);
return { root: canonicalCandidate, archiveExisting, existingState };
}
export interface PreparedSecurityOutput {
root: string;
archivedTo?: string;
}
export async function prepareSecurityOutputDirectory(
output: SecurityOutputPlan,
archiveSuffix: string = Bun.randomUUIDv7(),View on GitHub (pinned to 9690622007)