denoland/deno · error · FsError

Not a directory

Error message

Not a directory

What it means

In a compiled (deno compile) binary, paths inside the embedded VFS cannot become the real process working directory, so chdir is emulated: if the VFS target exists and is a directory, the call succeeds as a no-op (Deno.cwd() keeps reporting the old real directory); if the target exists but is not a directory, you get io::ErrorKind::NotADirectory 'Not a directory'.

Source

Thrown at cli/rt/file_system.rs:157

    RealFs.cwd()
  }

  fn tmp_dir(&self) -> FsResult<PathBuf> {
    RealFs.tmp_dir()
  }

  fn chdir(&self, path: &CheckedPath) -> FsResult<()> {
    if self.is_vfs_path(path) {
      // The process working directory can't actually be changed to a path
      // inside the embedded virtual file system, but applications (e.g.
      // Next.js standalone builds) commonly chdir into their own directory.
      // Verify the target exists and is a directory in the VFS and treat the
      // change as a no-op rather than failing with NotSupported. Note that
      // Deno.cwd() still reports the previous (real) working directory.
      if self.vfs.stat(path)?.as_fs_stat().is_directory {
        Ok(())
      } else {
        Err(FsError::Io(std::io::Error::new(
          ErrorKind::NotADirectory,
          "Not a directory",
        )))
      }
    } else {
      RealFs.chdir(path)
    }
  }

  fn umask(&self, mask: Option<u32>) -> FsResult<u32> {
    RealFs.umask(mask)
  }

  fn open_sync(
    &self,
    path: &CheckedPath,
    options: OpenOptions,
  ) -> FsResult<Rc<dyn DenoFile>> {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Point chdir at an actual directory (e.g. the directory of the entry module: new URL('.', import.meta.url))
  2. Guard with Deno.statSync first and check .isDirectory before chdir
  3. Remember that in compiled binaries chdir into the VFS is a no-op for the OS — code relying on the CWD actually changing (relative file writes) must instead resolve paths explicitly against the VFS root

Example fix

// before (compiled binary; mod.ts is a file)
Deno.chdir(new URL('.', import.meta.url).pathname + 'mod.ts');

// after
const dir = new URL('.', import.meta.url).pathname;
if (Deno.statSync(dir).isDirectory) Deno.chdir(dir);
Defensive patterns

Strategy: validation

Validate before calling

// Safe chdir that tolerates the compiled-VFS no-op semantics
import { isDir } from "./util.ts";
export function chdirSafe(p: string) {
  const st = Deno.statSync(p); // follows symlinks
  if (!st.isDirectory) throw new Error(`chdir target is not a directory: ${p}`);
  Deno.chdir(p);
}

Type guard

function isDirectorySync(p: string): boolean {
  try { return Deno.statSync(p).isDirectory; } catch { return false; }
}

Try / catch

try {
  process.chdir(dir); // or Deno.chdir(dir)
} catch (e) {
  if (e instanceof Error && /Not a directory/i.test(e.message)) {
    // derive the real directory of the current module instead
    Deno.chdir(new URL(".", import.meta.url).pathname);
  } else throw e;
}

Prevention

When it happens

Trigger: A compiled app calling Deno.chdir (or process.chdir in Node-compat code, or a dependency like Next.js standalone doing chdir(__dirname)) where the VFS path resolves to a file or symlink-to-file rather than a directory; e.g. chdir('/deno-dir/src/mod.ts') or a __dirname computed to point at a file.

Common situations: Ported Node scripts that chdir into paths derived from import.meta/require paths and landing on files; case-sensitivity or symlink differences making a path that is a dir on the host a file in the VFS; hardcoded absolute paths from the build machine baked into the compiled app.

Related errors


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