{"record":{"id":"03099200c5e62adc","repo":"can1357/oh-my-pi","slug":"security-output-directory-is-not-empty-enable-arc","errorCode":null,"errorMessage":"Security output directory is not empty; enable archiveExisting or choose another directory","messagePattern":"Security output directory is not empty; enable archiveExisting or choose another directory","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/preflight.ts","lineNumber":263,"sourceCode":"\tarchiveExisting: boolean,\n): Promise<SecurityOutputPlan> {\n\tconst requested = path.resolve(outputRoot);\n\tconst parent = await fs.realpath(path.dirname(requested));\n\tconst canonicalCandidate = path.join(parent, path.basename(requested));\n\tif (pathIsWithin(canonicalCandidate, repositoryRoot)) {\n\t\tthrow new Error(\"Security output directory must be outside the scanned repository\");\n\t}\n\tlet existingState: SecurityOutputPlan[\"existingState\"] = \"absent\";\n\ttry {\n\t\tconst stats = await fs.lstat(canonicalCandidate);\n\t\tif (stats.isSymbolicLink()) throw new Error(\"Security output directory must not be a symbolic link\");\n\t\tif (!stats.isDirectory()) throw new Error(\"Security output path exists and is not a directory\");\n\t\tconst real = await fs.realpath(canonicalCandidate);\n\t\tif (real !== canonicalCandidate) throw new Error(\"Security output directory does not have a canonical identity\");\n\t\tconst entries = await fs.readdir(canonicalCandidate);\n\t\texistingState = entries.length === 0 ? \"empty\" : \"archivable\";\n\t\tif (entries.length > 0 && !archiveExisting) {\n\t\t\tthrow new Error(\"Security output directory is not empty; enable archiveExisting or choose another directory\");\n\t\t}\n\t} catch (error) {\n\t\tif (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) throw error;\n\t\tawait fs.mkdir(canonicalCandidate, { recursive: false, mode: 0o700 });\n\t\texistingState = \"empty\";\n\t}\n\tif (process.platform !== \"win32\") await fs.chmod(canonicalCandidate, 0o700);\n\treturn { root: canonicalCandidate, archiveExisting, existingState };\n}\n\nexport interface PreparedSecurityOutput {\n\troot: string;\n\tarchivedTo?: string;\n}\n\nexport async function prepareSecurityOutputDirectory(\n\toutput: SecurityOutputPlan,\n\tarchiveSuffix: string = Bun.randomUUIDv7(),","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/preflight.ts#L245-L281","documentation":"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.","triggerScenarios":"Calling output()/normalizeOutput with archiveExisting=false (default) where readdir(canonicalCandidate) returns one or more entries.","commonSituations":"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.","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."],"exampleFix":"// before\nconst plan = await output({ outputRoot, repositoryRoot });\n// after\nconst plan = await output({ outputRoot, repositoryRoot, archiveExisting: true });","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs/promises\";\ntry {\n  const entries = await fs.readdir(outputPath);\n  if (entries.length > 0 && !archiveExisting) {\n    throw new Error(`Output dir not empty (${entries.length} entries); set archiveExisting or clean it`);\n  }\n} catch (e) {\n  if ((e as NodeJS.ErrnoException).code !== \"ENOENT\") throw e;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await output(options);\n} catch (err) {\n  if ((err as Error).message.includes(\"is not empty\")) {\n    options.archiveExisting = true;\n    return output(options); // retry, archiving old contents\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["filesystem","validation","security"],"backgroundTag":"directory-not-empty","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}