{"record":{"id":"5549121fad9e118b","repo":"denoland/deno","slug":"path-not-found-symlink-not-dir","errorCode":null,"errorMessage":"path not found (symlink not dir): {}","messagePattern":"path not found \\(symlink not dir\\): (.+?)","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"cli/rt/file_system.rs","lineNumber":1287,"sourceCode":"    for component in relative_path.components() {\n      let component = component.as_os_str();\n      let current_dir = match current_entry {\n        VfsEntryRef::Dir(dir) => {\n          final_path.push(component);\n          dir\n        }\n        VfsEntryRef::Symlink(symlink) => {\n          let dest = symlink.resolve_dest_from_root(&self.root_path);\n          let (resolved_path, entry) =\n            self.find_entry_inner(&dest, seen, case_sensitivity)?;\n          final_path = resolved_path; // overwrite with the new resolved path\n          match entry {\n            VfsEntryRef::Dir(dir) => {\n              final_path.push(component);\n              dir\n            }\n            _ => {\n              return Err(std::io::Error::new(\n                std::io::ErrorKind::NotFound,\n                format!(\"path not found (symlink not dir): {}\", path.display()),\n              ));\n            }\n          }\n        }\n        _ => {\n          return Err(std::io::Error::new(\n            std::io::ErrorKind::NotFound,\n            format!(\"path not found (not dir): {}\", path.display()),\n          ));\n        }\n      };\n      let component = component.to_string_lossy();\n      current_entry = current_dir\n        .entries\n        .get_by_name(&component, case_sensitivity)\n        .ok_or_else(|| {","sourceCodeStart":1269,"sourceCodeEnd":1305,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/cli/rt/file_system.rs#L1269-L1305","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check what the symlink actually points at: Deno.readLinkSync / Deno.statSync (which follows links) on the path without the extra child segments","Build paths from the resolved target: resolve the symlink first, then append remaining components to the resolved directory","Validate directory-ness of each ancestor (statSync(...).isDirectory) before appending children in generic path-walking code","If the VFS content is yours (custom compile inputs), avoid symlinks-to-files where consumers expect directories"],"exampleFix":"// before\nconst p = linkPath + '/index.js';      // linkPath is a symlink to a file\nDeno.readTextFileSync(p);              // path not found (symlink not dir)\n\n// after\nconst real = Deno.statSync(linkPath);  // follows the symlink\nconst base = real.isDirectory ? linkPath : stdDirOf(linkPath);\nDeno.readTextFileSync(base + '/index.js');","handlingStrategy":"validation","validationCode":"// Verify each ancestor is a directory (following symlinks) before descent\nfunction assertResolvableDirChain(p: string) {\n  const parts = p.split(\"/\").filter(Boolean);\n  let cur = \"\";\n  for (const part of parts) {\n    cur += `/${part}`;\n    const st = Deno.statSync(cur); // follows symlinks\n    if (!st.isDirectory && cur !== p) throw new Error(`non-directory in path: ${cur}`);\n  }\n}","typeGuard":"function isDirOrSymlinkToDir(p: string): boolean {\n  try { return Deno.statSync(p).isDirectory; } catch { return false; }\n}","tryCatchPattern":"try {\n  content = await Deno.readTextFile(`${linkPath}/${child}`);\n} catch (e) {\n  if (e instanceof Deno.errors.NotFound && isDirOrSymlinkToDir(linkPath) === false) {\n    // linkPath is a symlink to a file: resolve it and read the file directly\n    content = await Deno.readTextFile(Deno.realPathSync(linkPath));\n  } else throw e;\n}","preventionTips":["Resolve symlinks (realPathSync/statSync) before appending child segments","When walking npm-style trees, readLink first and branch on target type","Prefer building paths from resolved targets instead of link intermediates"],"tags":["compile","vfs","symlink","path-resolution","not-found"],"backgroundTag":"path-not-found","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}