jdx/mise · error

too many symbolic links in managed directory {}

Error message

too many symbolic links in managed directory {}

What it means

A symlink-loop / symlink-depth guard in open_or_create_directory_tree_inner: if resolving a managed directory path requires following more than 40 symlinks, the operation aborts. This prevents infinite loops caused by cyclic symlinks and bounds worst-case traversal. Without it, a malicious or broken symlink cycle could hang the operation.

Source

Thrown at src/system/managed_files.rs:1582

/// Open an absolute directory path one component at a time without following
/// symlinks, creating missing components with process-default metadata. The
/// returned descriptor binds later metadata changes to the directory that was
/// actually opened instead of resolving the path again.
#[cfg(unix)]
fn open_or_create_directory_tree(path: &Path) -> Result<std::os::fd::OwnedFd> {
    open_or_create_directory_tree_inner(path, 0)
}

#[cfg(unix)]
fn open_or_create_directory_tree_inner(
    path: &Path,
    followed_symlinks: usize,
) -> Result<std::os::fd::OwnedFd> {
    use nix::fcntl::{AtFlags, OFlag, open, openat};
    use nix::sys::stat::{Mode, SFlag, fstat, fstatat, mkdirat};

    if followed_symlinks > 40 {
        bail!(
            "too many symbolic links in managed directory {}",
            path.display()
        );
    }

    let components = path
        .strip_prefix(Path::new("/"))
        .wrap_err_with(|| format!("managed directory must be absolute: {}", path.display()))?
        .components()
        .map(|component| match component {
            std::path::Component::Normal(name) => Ok(name.to_os_string()),
            _ => bail!("invalid managed directory path: {}", path.display()),
        })
        .collect::<Result<Vec<_>>>()?;

    let flags = OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW;
    let mut directory = open(Path::new("/"), flags, Mode::empty())?;
    let mut current = PathBuf::from("/");

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Find the loop with `namei -l <path>` or `readlink -f <path>` and remove the cyclic symlink
  2. Recreate the affected symlinks pointing at the real target
  3. Reinstall/recreate the managed directory from a clean state
  4. Audit directories on PATH of managed locations for unexpected symlinks

Example fix

// before
$ ls -l /opt/tools/current
current -> latest; latest -> current  // loop
// after
$ rm /opt/tools/latest
$ ln -s v1.2.3 /opt/tools/latest
Defensive patterns

Strategy: validation

Validate before calling

fn symlink_chain_length(path: &Path, max: usize) -> std::io::Result<usize> {
    let mut current = path.to_path_buf();
    let mut count = 0;
    while let Ok(target) = std::fs::read_link(&current) {
        count += 1;
        if count > max { return Err(std::io::Error::new(std::io::ErrorKind::FilesystemLoop, "too many symlinks")); }
        current = if target.is_absolute() { target } else { current.parent().unwrap().join(target) };
    }
    Ok(count)
}

Type guard

fn has_symlink_loop(path: &Path) -> bool {
    std::fs::read_link(path).is_ok() && std::fs::canonicalize(path).is_err()
}

Try / catch

match result {
    Err(e) if e.to_string().contains("too many symbolic links") => diagnose_and_break_loop(path),
    Err(e) => return Err(e),
    Ok(v) => v,
}

Prevention

When it happens

Trigger: The managed directory path contains components that are symlinks whose targets chain (or loop) more than 40 links deep — e.g. `a -> b -> c -> ... -> a`, or deeply chained symlinks accumulated over time.

Common situations: Symlink loops created by misconfigured dotfile managers (stow, chezmoi, GNU stow conflicts); backup/restore tools that mangled symlinks; attacker-planted loops in a shared/world-writable directory.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/15da9ede819ae376. Report an issue: GitHub.