denoland/deno · error

Refusing to include {}: not a regular file (symlinks and spe

Error message

Refusing to include {}: not a regular file (symlinks and special files are excluded)

What it means

While collecting auto-included files (README/LICENSE at cli/tools/pack/mod.rs:391 and :413), `read_auto_included_file` uses `symlink_metadata` and refuses anything that is not a regular file. Symlinks and special files (FIFOs, devices) are excluded so the tarball cannot smuggle content from outside the package directory.

Source

Thrown at cli/tools/pack/mod.rs:374

}

/// Read an auto-included file (README/LICENSE) only if it is a regular
/// file in the package directory. We use `symlink_metadata` rather than
/// `Path::exists()` + `read()` so a symlink pointing outside the
/// package — e.g. a `LICENSE` symlink to `~/.ssh/id_rsa` — never gets
/// packed. Returns `Ok(None)` if the path does not exist or is not a
/// regular file; returns `Err` only on actual I/O failure when reading
/// a confirmed regular file.
fn read_auto_included_file(
  path: &std::path::Path,
) -> Result<Option<Vec<u8>>, AnyError> {
  let metadata = match std::fs::symlink_metadata(path) {
    Ok(m) => m,
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
    Err(e) => return Err(e.into()),
  };
  if !metadata.file_type().is_file() {
    bail!(
      "Refusing to include {}: not a regular file (symlinks and special files are excluded)",
      path.display()
    );
  }
  Ok(Some(std::fs::read(path)?))
}

fn collect_readme_license_files(
  package: &JsrPackageConfig,
) -> Result<Vec<ReadmeOrLicense>, AnyError> {
  let package_dir = package.config_file.dir_path();
  let mut files = Vec::new();

  // Look for README files (case-insensitive)
  for name in &["README.md", "README", "readme.md", "Readme.md", "readme"] {
    let path = package_dir.join(name);
    if let Some(content) = read_auto_included_file(&path)? {
      files.push(ReadmeOrLicense {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Replace the symlink with a real copy of the file (`cp -L` then remove the link).
  2. Generate the LICENSE/README per package at build time instead of linking.
  3. If the file is not meant to ship, delete or rename it so auto-include skips it (missing files return Ok(None)).

Example fix

# before
LICENSE -> ../../LICENSE
deno pack
# after
cp -L LICENSE LICENSE.tmp && mv -f LICENSE.tmp LICENSE
deno pack
Defensive patterns

Strategy: validation

Validate before calling

// Refuse to pack when README/LICENSE is a symlink
import { lstatSync } from "node:fs";
for (const f of ["README.md", "LICENSE"]) {
  try {
    const st = lstatSync(f);
    if (!st.isFile()) {
      console.error(`${f} is not a regular file; replace symlink before packing`);
      process.exit(1);
    }
  } catch { /* absent files are fine */ }
}

Type guard

import { lstatSync } from "node:fs";
const isRegularFile = (p: string): boolean => {
  try { return lstatSync(p).isFile(); } catch { return false; }
};

Prevention

When it happens

Trigger: README.md, README.md/LICENSE or LICENSE being a symlink (common in monorepo roots pointing at a shared LICENSE). A named pipe or other special file where README/LICENSE is expected.

Common situations: Workspaces that symlink a root LICENSE into each member; dotfiles managers (stow) that symlink READMEs; CI checkouts that materialize symlinks. True hardlinks are fine (still regular files), which is why only symlink/special cases hit it.

Related errors


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