{"record":{"id":"61a5d2c684424f71","repo":"denoland/deno","slug":"err-fs-cp-dir-to-non-dir","errorCode":"ERR_FS_CP_DIR_TO_NON_DIR","errorMessage":"Cannot overwrite non-directory with directory","messagePattern":"Cannot overwrite non-directory with directory","errorType":"exception","errorClass":"SystemError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/_fs/cp/cp.ts","lineNumber":68,"sourceCode":"const {\n  Number,\n  PromiseResolve,\n  SymbolAsyncIterator,\n} = primordials;\n\nfunction throwCpError(err) {\n  const { EEXIST, EINVAL, EISDIR, ENOTDIR } = lazyConstants();\n  switch (err.kind) {\n    case \"EINVAL\":\n      throw new ERR_FS_CP_EINVAL({\n        message: err.message,\n        path: err.path,\n        syscall: \"cp\",\n        errno: EINVAL,\n        code: \"EINVAL\",\n      });\n    case \"DIR_TO_NON_DIR\":\n      throw new ERR_FS_CP_DIR_TO_NON_DIR({\n        message: err.message,\n        path: err.path,\n        syscall: \"cp\",\n        errno: EISDIR,\n        code: \"EISDIR\",\n      });\n    case \"NON_DIR_TO_DIR\":\n      throw new ERR_FS_CP_NON_DIR_TO_DIR({\n        message: err.message,\n        path: err.path,\n        syscall: \"cp\",\n        errno: ENOTDIR,\n        code: \"ENOTDIR\",\n      });\n    case \"EEXIST\":\n      throw new ERR_FS_CP_EEXIST({\n        message: err.message,\n        path: err.path,","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/_fs/cp/cp.ts#L50-L86","documentation":"During fs.cp(src, dest), the native path check stats both entries; if src is a directory but an existing dest is not a directory, the copy is rejected with kind DIR_TO_NON_DIR, which throwCpError maps to ERR_FS_CP_DIR_TO_NON_DIR ('Cannot overwrite non-directory with directory', errno EISDIR). Node refuses because writing the directory's entries onto a regular file would corrupt the destination.","triggerScenarios":"fs.cp('src-dir/', 'existing-file.txt', { recursive: true }) — recursing a directory onto a plain file; dest previously created as an empty touch-file placeholder (common in provisioning scripts) that a later directory copy targets.","commonSituations":"Deploy scripts that 'reserve' an output path by touching a file, then copy a directory to it; switching a copy target from file to directory between releases without cleaning the workspace; cache dirs whose marker file was left behind.","solutions":["Check dest first: if it exists and is a file, unlink or rename it before the directory copy","Use a fresh destination path or clean the target directory between runs (rm -rf && cp)","Guard with fs.stat and branch: copy dir-vs-dir or file-vs-file deliberately"],"exampleFix":"// before\nfs.cp('dist/', 'release', { recursive: true }, cb); // 'release' is an existing file\n\n// after\nconst st = fs.statSync('release', { throwIfNoEntry: false });\nif (st?.isFile()) fs.rmSync('release');\nfs.cp('dist/', 'release', { recursive: true }, cb);","handlingStrategy":"try-catch","validationCode":"const st = fs.statSync(dest, { throwIfNoEntry: false });\nif (st && !st.isDirectory()) throw new Error('cp: dest is a file but src is a directory');","typeGuard":null,"tryCatchPattern":"try { await fs.promises.cp(srcDir, dest, { recursive: true }); } catch (e) { if (e.code === 'ERR_FS_CP_DIR_TO_NON_DIR') { fs.rmSync(dest); return fs.promises.cp(srcDir, dest, { recursive: true }); } throw e; }","preventionTips":["Clean stale placeholder files before directory copies","Assert kind compatibility (dir-to-dir, file-to-file) in deploy scripts","Treat marker/touch files as cache invalidation, not copy targets"],"tags":["node-compat","fs","cp","copy","err-fs-cp-dir-to-non-dir","eisdir","deno"],"backgroundTag":"fs-copy-type-mismatch","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}