jdx/mise · error
existing symbolic link conflicts with adoption
Error message
existing symbolic link conflicts with adoption: {entry} What it means
When adopting a non-empty destination directory as the global-configuration repository, mise walks every path component of every file in the transferred tree and rejects the adoption if any ancestor of an entry in the destination is a symbolic link. Symlinked ancestors could redirect file writes outside the destination, so adoption is aborted to keep the operation safe and contained.
Solutions
- Remove the conflicting symlink(s): `find <destination> -type l` to list them, then `unlink <symlink>` or `rm <symlink>`.
- Replace symlinks with real directories, copying content back if needed: `cp -rL <link> <tmp> && rm <link> && mv <tmp> <dir>`.
- If a dotfiles manager owns the links, exclude the global-config directory from it or restructure the layout.
- Re-run the install after no ancestor of any tracked entry is a symlink.
Example fix
// before $ ls -l ~/.config/mise/tasks tasks -> ~/dotfiles/mise-tasks // after $ unlink ~/.config/mise/tasks $ cp -r ~/dotfiles/mise-tasks ~/.config/mise/tasks
Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs'), path = require('path');
for (const entry of trackedFiles) {
let dir = dest;
for (const part of entry.split('/').slice(0, -1)) {
dir = path.join(dir, part);
if (fs.lstatSync(dir).isSymbolicLink()) throw new Error('symlinked ancestor blocks adoption: ' + dir);
}
} Try / catch
try {
await installSource(bundle, origin);
} catch (e) {
if (/symbolic link conflicts/.test(String(e.cause ?? e))) {
console.error('replace symlinked directories inside the destination with real directories');
}
throw e;
} Prevention
- Do not symlink subdirectories inside the global-config destination; keep real files there
- If using GNU Stow/chezmoi, exclude the global-config directory from symlink farm management
- Run `find <dest> -type l` before adopting a directory
When it happens
Trigger: install/install_source/preview_source (install_at) into a non-empty directory without `.git`, where any parent directory of a tracked entry (e.g. `<dest>/config` when adopting file `config/settings.toml`) is a symlink on disk.
Common situations: Users symlink directories like `~/.config/mise/tasks` to a dotfiles-managed location; a dotfiles manager (GNU Stow, chezmoi) created symlinks inside the destination; `settings` or subdirectories were symlinked for sharing across machines.
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
- adoption requires confirmation; review the directory and…
- existing file conflicts with adoption
- formula uses `# `, which mise's source-build shim does not…
- is an existing repository without the mise marker; `mise…
- is an existing repository without the mise marker; `mise…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/411fc047211b5b40.
Report an issue: GitHub.
Appendix: 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 533346cc37)