{"record":{"id":"9b6c6667ad2099fc","repo":"denoland/deno","slug":"err-fs-eisdir-9b6c66","errorCode":"ERR_FS_EISDIR","errorMessage":"Path is a directory: rm returned EISDIR (is a directory) ${path}","messagePattern":"Path is a directory: rm returned EISDIR \\(is a directory\\) (.+?)","errorType":"exception","errorClass":"SystemError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal/fs/utils.mjs","lineNumber":1078,"sourceCode":"  },\n);\n\n/** @type {(path: string, options: RmOptions, expectDir: boolean) => RmOptions | false} */\nexport const validateRmOptionsSync = hideStackFrames(\n  (path, options, expectDir) => {\n    options = validateRmdirOptions(options, defaultRmOptions);\n    validateBoolean(options.force, \"options.force\");\n\n    if (!options.force || expectDir || !options.recursive) {\n      const isDirectory = lstatSync(path, { throwIfNoEntry: !options.force })\n        ?.isDirectory();\n\n      if (expectDir && !isDirectory) {\n        return false;\n      }\n\n      if (isDirectory && !options.recursive) {\n        throw new ERR_FS_EISDIR({\n          code: \"EISDIR\",\n          message: \"is a directory\",\n          path,\n          syscall: \"rm\",\n          errno: osConstants.errno.EISDIR,\n        });\n      }\n    }\n\n    return options;\n  },\n);\n\n// Lazy: reading `lazyProcess().default` at eval time TDZs while node:process\n// is itself bootstrapping (cold node-defer path). Resolve on first warning.\nlet recursiveRmdirWarned;\nexport function emitRecursiveRmdirWarning() {\n  recursiveRmdirWarned ??= lazyProcess().default.noDeprecation;","sourceCodeStart":1060,"sourceCodeEnd":1096,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal/fs/utils.mjs#L1060-L1096","documentation":"Thrown by Deno's node:fs compatibility layer when fs.rm/fs.rmSync (and fs.promises.rm) targets an existing directory without { recursive: true }. The polyfill runs lstatSync whenever force is false, rm is called from the rmdir-style path (expectDir), or recursive is unset; if the target is a directory and recursive is falsy it raises ERR_FS_EISDIR with errno EISDIR and syscall 'rm'. Note that { force: true } alone does NOT suppress this: the check still runs because !options.recursive is true. It mirrors Node.js's refusal to delete directories non-recursively.","triggerScenarios":"fs.rmSync('/tmp/dir') or await fs.promises.rm(dirPath) on an existing directory; fs.rm(dir, callback) with no options; fs.rmSync(dir, { force: true }) without recursive (EISDIR still thrown); passing a file path variable that actually points to a directory (e.g. a temp path created with mkdir).","commonSituations":"Cleanup scripts deleting temp/build directories (dist/, node_modules/.cache) written for the old fs.rmdir recursive behavior; config or CLI code where the path comes from user input that may be a directory; CI steps that remove workspaces; passing options in the wrong argument position so recursive is never received.","solutions":["Pass { recursive: true } when the target may be a directory: fs.rmSync(dir, { recursive: true, force: true })","If you only expect an empty directory, use fs.rmdir/fs.rmdirSync instead","Pre-check with fs.lstatSync(path)?.isDirectory() and branch to recursive removal","If the path may not exist, also pass force: true to avoid a companion ENOENT throw"],"exampleFix":"// before\nfs.rmSync(targetPath);\n\n// after\nfs.rmSync(targetPath, { recursive: true, force: true });","handlingStrategy":"validation","validationCode":"import * as fs from 'node:fs';\n\nfunction removePath(p: string) {\n  const st = fs.lstatSync(p, { throwIfNoEntry: false });\n  if (st?.isDirectory()) fs.rmSync(p, { recursive: true, force: true });\n  else fs.rmSync(p, { force: true });\n}","typeGuard":"const isDirectory = (p: string) =>\n  fs.lstatSync(p, { throwIfNoEntry: false })?.isDirectory() ?? false;","tryCatchPattern":"try {\n  fs.rmSync(p);\n} catch (err) {\n  if (err?.code === 'ERR_FS_EISDIR' || err?.code === 'EISDIR') {\n    fs.rmSync(p, { recursive: true, force: true });\n  } else {\n    throw err;\n  }\n}","preventionTips":["Default to { recursive: true, force: true } in cleanup helpers","Treat rm targets as untrusted paths: lstat before choosing the removal strategy","Do not rely on force:true to suppress EISDIR — only recursive:true does"],"tags":["fs","node-compat","deno","filesystem","eisdir"],"backgroundTag":"fs-eisdir","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}