denoland/deno · error · NodeSystemError
ERR_FS_EISDIR
ERR_FS_EISDIR
Error message
Path is a directory
What it means
Inside fs.cp, when the native op reports kind 'EISDIR', throwCpError rethrows it as ERR_FS_EISDIR with default message 'Path is a directory': an operation that requires a plain file (open/write path taken by the file-copy branch) hit a directory during the copy — e.g. the destination turned out to be a directory where a file write was attempted in a combination not caught by the DIR/NON_DIR pre-checks.
Source
Thrown at ext/node/polyfills/_fs/cp/cp.ts:92
});
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,
syscall: "cp",
errno: EEXIST,
code: "EEXIST",
});
case "EISDIR":
throw new ERR_FS_EISDIR({
message: err.message,
path: err.path,
syscall: "cp",
errno: EISDIR,
code: "EISDIR",
});
case "SOCKET":
throw new ERR_FS_CP_SOCKET({
message: err.message,
path: err.path,
syscall: "cp",
errno: EINVAL,
code: "EINVAL",
});
case "FIFO":
throw new ERR_FS_CP_FIFO_PIPE({
message: err.message,
path: err.path,View on GitHub (pinned to 89f33cbef2)
Solutions
- Re-stat src and dest with fsp.lstat right before copying and bail or clean on mismatch
- Avoid concurrent copies to the same destination (serialize with a lock or unique temp names)
- Retry once after removing the conflicting dest if your tooling allows it
Example fix
// before
await fsp.cp(src, dest, opts); // concurrent job made dest a directory mid-run
// after
const st = await fsp.lstat(dest).catch(() => null);
if (st?.isDirectory() && !(await fsp.lstat(src)).isDirectory()) {
await fsp.rm(dest, { recursive: true, force: true });
}
await fsp.cp(src, dest, opts); Defensive patterns
Strategy: retry
Validate before calling
const [s, d] = await Promise.all([fsp.lstat(src), fsp.lstat(dest).catch(() => null)]);
if (d?.isDirectory() && !s.isDirectory()) await fsp.rm(dest, { recursive: true, force: true }); Try / catch
try {
await fsp.cp(src, dest, opts);
} catch (err) {
if (err.code === 'ERR_FS_EISDIR' && !err.message.includes('not copied')) {
await fsp.rm(dest, { recursive: true, force: true });
await fsp.cp(src, dest, opts); // one retry after clearing conflicting dest
} else throw err;
} Prevention
- Serialize concurrent copies that target the same dest path
- Copy to a temp name and rename atomically when races are possible
- Validate src/dest types immediately before the copy, not long before
When it happens
Trigger: Race where dest is created as a directory between validation and write; copying with options that take the on-file branch while dest resolves to a directory (e.g. through symlinks/dereference combos the native op rejects with EISDIR).
Common situations: Concurrent build jobs writing the same output path; symlinked destination directories with dereference enabled; transient states during watch-mode rebuilds.
Related errors
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/d8f84c505860ad1d.
Report an issue: GitHub.