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
- Find the offending link: run the command with dry-run/preview, or `find <destination> -type l` to list symlinks under the destination.
- 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.
- Remove the corresponding path from the transferred source repository if that entry should not be transferred at all.
- 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
- Keep the global config directory free of symlinks; copy instead of link.
- Run `find <dest> -type l` before onboarding an existing directory.
- Exclude symlinked subdirectories from the transferred repository layout.
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
- brew-cask: staged symlink path escaped extraction root: {}
- brew-cask: refusing generic artifact source outside the extr
- expected world-writable ancestor to be refused
- expected symlinked appdir tail to be rejected
- expected pre-planted symlink destination to be refused
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/411fc047211b5b40.
Report an issue: GitHub.