denoland/deno · error · SystemError

ERR_FS_CP_DIR_TO_NON_DIR

ERR_FS_CP_DIR_TO_NON_DIR

Error message

Cannot overwrite non-directory with directory

What it means

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.

Solutions

  1. Check dest first: if it exists and is a file, unlink or rename it before the directory copy
  2. Use a fresh destination path or clean the target directory between runs (rm -rf && cp)
  3. Guard with fs.stat and branch: copy dir-vs-dir or file-vs-file deliberately

Example fix

// before
fs.cp('dist/', 'release', { recursive: true }, cb); // 'release' is an existing file

// after
const st = fs.statSync('release', { throwIfNoEntry: false });
if (st?.isFile()) fs.rmSync('release');
fs.cp('dist/', 'release', { recursive: true }, cb);
Defensive patterns

Strategy: try-catch

Validate before calling

const st = fs.statSync(dest, { throwIfNoEntry: false });
if (st && !st.isDirectory()) throw new Error('cp: dest is a file but src is a directory');

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/61a5d2c684424f71. Report an issue: GitHub.

Appendix: source

Thrown at ext/node/polyfills/_fs/cp/cp.ts:68

const {
  Number,
  PromiseResolve,
  SymbolAsyncIterator,
} = primordials;

function throwCpError(err) {
  const { EEXIST, EINVAL, EISDIR, ENOTDIR } = lazyConstants();
  switch (err.kind) {
    case "EINVAL":
      throw new ERR_FS_CP_EINVAL({
        message: err.message,
        path: err.path,
        syscall: "cp",
        errno: EINVAL,
        code: "EINVAL",
      });
    case "DIR_TO_NON_DIR":
      throw new ERR_FS_CP_DIR_TO_NON_DIR({
        message: err.message,
        path: err.path,
        syscall: "cp",
        errno: EISDIR,
        code: "EISDIR",
      });
    case "NON_DIR_TO_DIR":
      throw new ERR_FS_CP_NON_DIR_TO_DIR({
        message: err.message,
        path: err.path,
        syscall: "cp",
        errno: ENOTDIR,
        code: "ENOTDIR",
      });
    case "EEXIST":
      throw new ERR_FS_CP_EEXIST({
        message: err.message,
        path: err.path,

View on GitHub (pinned to 9ad36f7a2c)