{"record":{"id":"6d689f0beef359e1","repo":"denoland/deno","slug":"refusing-to-materialize-package-into-symlinked-dir","errorCode":null,"errorMessage":"refusing to materialize package into symlinked directory","messagePattern":"refusing to materialize package into symlinked directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/npm_cache/fs_util.rs","lineNumber":74,"sourceCode":"where\n  TSys: FsCreateDirAll + FsMetadata,\n{\n  ensure_not_symlink(sys, path)?;\n  sys.fs_create_dir_all(path)?;\n  ensure_not_symlink(sys, path)\n}\n\n/// Returns an error when the path is a symlink.\npub fn ensure_not_symlink<TSys>(\n  sys: &TSys,\n  path: &Path,\n) -> Result<(), std::io::Error>\nwhere\n  TSys: FsMetadata,\n{\n  match sys.fs_symlink_metadata(path) {\n    Ok(metadata) if metadata.file_type().is_symlink() => {\n      Err(std::io::Error::new(\n        ErrorKind::AlreadyExists,\n        \"refusing to materialize package into symlinked directory\",\n      ))\n    }\n    Ok(_) => Ok(()),\n    Err(err) if err.kind() == ErrorKind::NotFound => Ok(()),\n    Err(err) => Err(err),\n  }\n}\n\n#[sys_traits::auto_impl]\npub trait HardLinkFileSys: FsHardLink + FsRemoveFile + ThreadSleep {}\n\n/// Hardlinks a file from one location to another.\npub fn hard_link_file<TSys: HardLinkFileSys>(\n  sys: &TSys,\n  from: &Path,\n  to: &Path,","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/libs/npm_cache/fs_util.rs#L56-L92","documentation":"ensure_not_symlink() is called before an npm package is extracted/materialized into its cache directory. If fs_symlink_metadata shows the target path is a symlink, it refuses with ErrorKind::AlreadyExists and this message, because writing package files through a symlink would scatter them into an unintended location and could silently mutate whatever the link points at. A missing path (NotFound) is fine; only an existing symlink is rejected.","triggerScenarios":"Extracting an npm tarball into $DENO_DIR/npm/... (or any configured npm cache dir) when the package's version directory, or a parent of it, is a symlink — e.g. someone symlinked the cache to another disk, or a backup/sync tool (iCloud, Dropbox) replaced directories with links.","commonSituations":"Symlinking DENO_DIR or the npm cache subdirectory to save disk space; shared caches between machines via network volumes; macOS file providers converting folders to symlink placeholders; deliberately symlinking node_modules in monorepos while the cache path collides with it.","solutions":["Run ls -la on the path from the error context and replace the symlink with a real directory (mkdir, then copy the contents back if needed).","Point DENO_DIR (or the npm cache setting) at a location that contains no symlinks anywhere along the path.","If the symlink came from a sync/backup tool, exclude the cache directory from that tool and restore a real directory."],"exampleFix":"# before\nln -s /mnt/big/deno-npm ~/.cache/deno/npm   # every tarball extract now fails\n\n# after\nrm ~/.cache/deno/npm\nmkdir -p ~/.cache/deno/npm   # real directory; extraction succeeds","handlingStrategy":"validation","validationCode":"use std::fs;\n\nfn cache_target_is_clean(path: &std::path::Path) -> std::io::Result<()> {\n  match fs::symlink_metadata(path) {\n    Ok(md) if md.file_type().is_symlink() => Err(std::io::Error::new(\n      std::io::ErrorKind::AlreadyExists,\n      format!(\"{} is a symlink; replace it with a real directory\", path.display()),\n    )),\n    Ok(_) => Ok(()),\n    Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),\n    Err(e) => Err(e),\n  }\n}","typeGuard":null,"tryCatchPattern":"match ensure_cache_dir(&path) {\n  Ok(()) => { /* extract */ }\n  Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists\n    && err.to_string().contains(\"symlink\") =>\n  {\n    // tell the user to replace the symlinked cache dir with a real directory\n  }\n  Err(err) => return Err(err),\n}","preventionTips":["Never symlink DENO_DIR or its npm subdirectory; if space is the concern, move the whole DENO_DIR to the bigger disk and set the env var to the real path.","Exclude the Deno cache from sync/backup tools (iCloud, Dropbox) that can replace directories with symlinks.","In setup scripts, assert the cache path is a real directory (fs::symlink_metadata check) before installs run."],"tags":["npm","cache","symlink","filesystem","deno"],"backgroundTag":"cache-path-is-symlink","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}