denoland/deno · error · std::io::Error

path not found (symlink not dir): {}

Error message

path not found (symlink not dir): {}

What it means

During VFS path traversal, when a path component lands on a symlink the resolver follows it (recursively, with cycle detection) and expects the resolved entry to be a directory so traversal can continue. If the symlink resolves to a file (or the recursion returns a non-directory), resolution fails with io::ErrorKind::NotFound 'path not found (symlink not dir): <p>' — the requested path implies directory components under a symlink that points at a file.

Source

Thrown at cli/rt/file_system.rs:1287

    for component in relative_path.components() {
      let component = component.as_os_str();
      let current_dir = match current_entry {
        VfsEntryRef::Dir(dir) => {
          final_path.push(component);
          dir
        }
        VfsEntryRef::Symlink(symlink) => {
          let dest = symlink.resolve_dest_from_root(&self.root_path);
          let (resolved_path, entry) =
            self.find_entry_inner(&dest, seen, case_sensitivity)?;
          final_path = resolved_path; // overwrite with the new resolved path
          match entry {
            VfsEntryRef::Dir(dir) => {
              final_path.push(component);
              dir
            }
            _ => {
              return Err(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("path not found (symlink not dir): {}", path.display()),
              ));
            }
          }
        }
        _ => {
          return Err(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("path not found (not dir): {}", path.display()),
          ));
        }
      };
      let component = component.to_string_lossy();
      current_entry = current_dir
        .entries
        .get_by_name(&component, case_sensitivity)
        .ok_or_else(|| {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Check what the symlink actually points at: Deno.readLinkSync / Deno.statSync (which follows links) on the path without the extra child segments
  2. Build paths from the resolved target: resolve the symlink first, then append remaining components to the resolved directory
  3. Validate directory-ness of each ancestor (statSync(...).isDirectory) before appending children in generic path-walking code
  4. If the VFS content is yours (custom compile inputs), avoid symlinks-to-files where consumers expect directories

Example fix

// before
const p = linkPath + '/index.js';      // linkPath is a symlink to a file
Deno.readTextFileSync(p);              // path not found (symlink not dir)

// after
const real = Deno.statSync(linkPath);  // follows the symlink
const base = real.isDirectory ? linkPath : stdDirOf(linkPath);
Deno.readTextFileSync(base + '/index.js');
Defensive patterns

Strategy: validation

Validate before calling

// Verify each ancestor is a directory (following symlinks) before descent
function assertResolvableDirChain(p: string) {
  const parts = p.split("/").filter(Boolean);
  let cur = "";
  for (const part of parts) {
    cur += `/${part}`;
    const st = Deno.statSync(cur); // follows symlinks
    if (!st.isDirectory && cur !== p) throw new Error(`non-directory in path: ${cur}`);
  }
}

Type guard

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

Try / catch

try {
  content = await Deno.readTextFile(`${linkPath}/${child}`);
} catch (e) {
  if (e instanceof Deno.errors.NotFound && isDirOrSymlinkToDir(linkPath) === false) {
    // linkPath is a symlink to a file: resolve it and read the file directly
    content = await Deno.readTextFile(Deno.realPathSync(linkPath));
  } else throw e;
}

Prevention

When it happens

Trigger: A compiled app addressing something like <dir-symlink-to-file>/child.js where the VFS contains a symlink (e.g. npm package bin or .bin style links) pointing to a file, but the code treats the link itself as a directory; path joining that appends segments onto a symlinked file path.

Common situations: Node-compat tooling walking node_modules/.bin or package 'main' symlinks and assuming directories; building paths from import.meta.url chains that pass through symlinked entries inside npm packages in the VFS; case-mismatch making an intended dir lookup land on a file link.

Related errors


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