denoland/deno · error

refusing to materialize package into symlinked directory

Error message

refusing to materialize package into symlinked directory

What it means

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.

Source

Thrown at libs/npm_cache/fs_util.rs:74

where
  TSys: FsCreateDirAll + FsMetadata,
{
  ensure_not_symlink(sys, path)?;
  sys.fs_create_dir_all(path)?;
  ensure_not_symlink(sys, path)
}

/// Returns an error when the path is a symlink.
pub fn ensure_not_symlink<TSys>(
  sys: &TSys,
  path: &Path,
) -> Result<(), std::io::Error>
where
  TSys: FsMetadata,
{
  match sys.fs_symlink_metadata(path) {
    Ok(metadata) if metadata.file_type().is_symlink() => {
      Err(std::io::Error::new(
        ErrorKind::AlreadyExists,
        "refusing to materialize package into symlinked directory",
      ))
    }
    Ok(_) => Ok(()),
    Err(err) if err.kind() == ErrorKind::NotFound => Ok(()),
    Err(err) => Err(err),
  }
}

#[sys_traits::auto_impl]
pub trait HardLinkFileSys: FsHardLink + FsRemoveFile + ThreadSleep {}

/// Hardlinks a file from one location to another.
pub fn hard_link_file<TSys: HardLinkFileSys>(
  sys: &TSys,
  from: &Path,
  to: &Path,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. 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).
  2. Point DENO_DIR (or the npm cache setting) at a location that contains no symlinks anywhere along the path.
  3. If the symlink came from a sync/backup tool, exclude the cache directory from that tool and restore a real directory.

Example fix

# before
ln -s /mnt/big/deno-npm ~/.cache/deno/npm   # every tarball extract now fails

# after
rm ~/.cache/deno/npm
mkdir -p ~/.cache/deno/npm   # real directory; extraction succeeds
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;

fn cache_target_is_clean(path: &std::path::Path) -> std::io::Result<()> {
  match fs::symlink_metadata(path) {
    Ok(md) if md.file_type().is_symlink() => Err(std::io::Error::new(
      std::io::ErrorKind::AlreadyExists,
      format!("{} is a symlink; replace it with a real directory", path.display()),
    )),
    Ok(_) => Ok(()),
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
    Err(e) => Err(e),
  }
}

Try / catch

match ensure_cache_dir(&path) {
  Ok(()) => { /* extract */ }
  Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists
    && err.to_string().contains("symlink") =>
  {
    // tell the user to replace the symlinked cache dir with a real directory
  }
  Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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