denoland/deno · error

Circular symlink detected: {} -> {}

Error message

Circular symlink detected: {} -> {}

What it means

When deno compile builds the virtual filesystem for a standalone binary, it follows directory symlinks while tracking visited paths in a set. Revisiting a path means a symlink cycle; the VFS builder bails with the chain of visited targets plus the offending link, since packing a cycle would never terminate.

Source

Thrown at cli/lib/standalone/virtual_fs.rs:1187

        VfsEntry::Symlink(VirtualSymlink {
          name: name.to_string(),
          dest_parts: VirtualSymlinkParts::from_path(target.as_ref()),
          dest_is_dir,
        })
      },
      |_| {
        // ignore previously inserted
      },
    );
    #[allow(clippy::disallowed_methods, reason = "ok, creating vfs")]
    let target_metadata = std::fs::symlink_metadata(target.as_ref())
      .with_context(|| {
        format!("Reading symlink target '{}'", target.display())
      })?;
    if target_metadata.is_symlink() {
      if !visited.insert(target.as_ref().to_path_buf()) {
        // todo: probably don't error in this scenario
        bail!(
          "Circular symlink detected: {} -> {}",
          visited
            .iter()
            .map(|p| p.display().to_string())
            .collect::<Vec<_>>()
            .join(" -> "),
          target.display()
        );
      }
      self.add_symlink_inner(&target, visited)
    } else if target_metadata.is_dir() {
      Ok(SymlinkTarget::Dir(target.0.into_owned()))
    } else {
      Ok(SymlinkTarget::File(target.0.into_owned()))
    }
  }

  /// Adds the CJS export analysis to the provided file.

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Break the cycle: replace one side of the loop with a real directory or file
  2. Point symlinks at concrete targets outside the packed tree, or remove them
  3. Exclude the cyclical directory from the embedded set and ship those assets alongside the binary instead

Example fix

# before
ln -s ../b a
ln -s ../a b   # compile fails: Circular symlink detected: a -> b -> a

# after
# make one side a real directory
cp -RL a a.real && rm a && mv a.real a
Defensive patterns

Strategy: validation

Validate before calling

# Detect symlink cycles before deno compile
find . -type l -exec realpath -e {} \; 2>/dev/null | sort | uniq -d | while read -r t; do
  echo "multiple links resolve to $t - check for a cycle"
done
# or list directory symlinks that point back into the tree
find . -type l | while read -r l; do
  case "$(readlink "$l")" in 
    .*|/*"$PWD"*) echo "suspicious link: $l -> $(readlink "$l")";;
  esac
done

Prevention

When it happens

Trigger: deno compile over a tree where symlinked directories form a loop - a -> b and b -> a, or a link pointing at an ancestor directory that is itself being packed.

Common situations: Monorepos with circular convenience links; alias directories created by tooling; manual links (ln -s ..) inside directories that get embedded.

Related errors


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