jdx/mise · error

existing symbolic link conflicts with adoption: {entry}

Error message

existing symbolic link conflicts with adoption: {entry}

What it means

When adopting a non-empty destination directory (no `.git` present), install_at walks every path component of every file coming from the transferred repository and rejects the adoption if any ancestor within the existing directory is a symbolic link. Symlinks could redirect the rename of transferred files outside the intended directory, so the operation is treated as unsafe and aborted.

Source

Thrown at src/system/remote_repository.rs:329

                    &format!("refs/remotes/origin/{branch}"),
                    revision,
                ],
            )?;
        } else if dry_run {
            miseprintln!(
                "Would keep the existing checkout of {origin} at {shown}; --update fast-forwards it"
            );
        }
        return Ok(destination.to_path_buf());
    }
    let nonempty = destination.exists() && destination.read_dir()?.next().is_some();
    if nonempty {
        for entry in entries.split('\0').filter(|s| !s.is_empty()) {
            let mut ancestor = destination.to_path_buf();
            for component in Path::new(entry).components() {
                ancestor.push(component);
                if ancestor.is_symlink() {
                    bail!("existing symbolic link conflicts with adoption: {entry}");
                }
            }
            let existing = destination.join(entry);
            if existing.exists()
                && (checkout.join(entry).is_symlink()
                    || !existing.is_file()
                    || std::fs::read(&existing)? != std::fs::read(checkout.join(entry))?)
            {
                bail!("existing file conflicts with adoption: {entry}");
            }
        }
        if dry_run {
            let new_files = entries
                .split('\0')
                .filter(|s| !s.is_empty())
                .filter(|entry| !destination.join(entry).exists())
                .count();
            miseprintln!(

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Find the offending link: run the command with dry-run/preview, or `find <destination> -type l` to list symlinks under the destination.
  2. Replace the symlink with a real directory (e.g. copy its contents and `rm` the link), or remove the symlink if it is not needed.
  3. Remove the corresponding path from the transferred source repository if that entry should not be transferred at all.
  4. Transfer into a fresh empty directory instead of adopting the existing symlink-laden layout.

Example fix

// before: ~/.config/mise/plugins is a symlink
ls -la ~/.config/mise   # plugins -> ~/dotfiles/plugins
rm ~/.config/mise/plugins && cp -r ~/dotfiles/plugins ~/.config/mise/plugins
// after: no symlinks in the path; adoption proceeds
Defensive patterns

Strategy: validation

Validate before calling

import { execFileSync } from 'child_process';
const links = execFileSync('find', [dest, '-type', 'l']).toString().trim();
if (links.length > 0) throw new Error(`symlinks under destination block adoption:\n${links}`);

Prevention

When it happens

Trigger: Installing into an existing non-empty directory where any prefix of a transferred file's path (or the file entry path itself) is a symlink — e.g. `~/.config/mise` contains a symlinked subdirectory like `plugins -> ~/dotfiles/plugins` that a transferred entry also writes into.

Common situations: Users symlink parts of their config directory into a dotfiles repo; package managers or tools created symlinked subdirectories inside the config path; a symlink loop or link to shared state exists in the tree.

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/411fc047211b5b40. Report an issue: GitHub.