jdx/mise · error
target symlink points to {}, use --force to remove it
Error message
target symlink points to {}, use --force to remove it What it means
For a `symlink`-mode entry, unapply removes the target only when the link provably belongs to mise: its `read_link` destination equals the configured `source`, or both resolve to the same file (`points_at_same_file`). A symlink pointing elsewhere is treated as foreign — the error names the current destination — and `--force` is required to remove it.
Source
Thrown at src/system/files.rs:1537
let mut paths = IndexMap::<PathBuf, ()>::new();
let mut cleanup_empty_dirs = false;
let mut conditional = false;
let mut clear_symlink_each_state = false;
match req.mode {
// A Windows file link that came out as a copy is planned by content further down; one
// that is a real symlink belongs here, where the link target is what identifies it as
// ours. `plan_expected_content` requires a non-symlink, so routing a symlink there
// would make unapply demand `--force`.
FileMode::Symlink
if !(cfg!(windows) && req.source.is_file() && !req.target.is_symlink()) =>
{
if req.target.is_symlink() {
let dest = std::fs::read_link(&req.target)?;
if opts.force || dest == req.source || points_at_same_file(&req.target, &req.source)
{
paths.insert(req.target.clone(), ());
} else {
bail!(
"target symlink points to {}, use --force to remove it",
dest.display_user()
);
}
} else if req.target.exists() {
if opts.force {
paths.insert(req.target.clone(), ());
} else {
bail!("target is not the managed symlink, use --force to remove it");
}
}
}
FileMode::SymlinkEach if !cfg!(windows) => {
clear_symlink_each_state = symlink_each_state_path(req).exists();
if req.target.is_symlink() || (req.target.exists() && !req.target.is_dir()) {
if opts.force {
paths.insert(req.target.clone(), ());
} else {View on GitHub (pinned to 6f52dcdf99)
Solutions
- Run `readlink <target>` and compare with the entry's `source` in mise.toml; fix whichever is wrong, or re-apply so mise recreates the link
- If the link is genuinely yours and should go, re-run with `--force`
- If the link belongs to another tool, remove the `[dotfiles]` entry instead of forcing deletion
Example fix
# before: target points elsewhere than the configured source $ readlink ~/.vimrc /home/me/other/vimrc # mise.toml says source = "~/dotfiles/vimrc" $ mise bootstrap dotfiles unapply Error: target symlink points to /home/me/other/vimrc, use --force to remove it # after: source of truth is ~/dotfiles/vimrc -> re-apply first $ mise bootstrap dotfiles apply # recreates the correct symlink $ mise bootstrap dotfiles unapply
Defensive patterns
Strategy: validation
Validate before calling
# Pre-check: every symlink-mode entry's target must point at its source.
check() {
src=$(realpath "$1"); tgt_link=$(readlink "$2" 2>/dev/null)
[ "$tgt_link" ] && [ "$(realpath "$2")" = "$src" ] || echo "MISMATCH: $2 -> $tgt_link (want $src)"
}
check ~/dotfiles/vimrc ~/.vimrc Type guard
fn is_foreign_symlink(err: &miette::Report) -> bool {
err.to_string()
.starts_with("target symlink points to")
} Prevention
- Let only one tool manage a given target; do not mix mise with stow/home-manager on the same files
- When renaming a source path in mise.toml, re-apply immediately so links are re-pointed
- Audit with `mise bootstrap dotfiles status` before unapplying
When it happens
Trigger: `unapply` of an entry whose target is a symlink whose read_link destination differs from `req.source` and does not resolve to the same file: the link was re-pointed manually, or the entry's `source` path in mise.toml changed after the link was created.
Common situations: Another dotfile manager owns the link; user re-pointed ~/.vimrc at a different config; source renamed in the repo without re-applying; absolute vs relative path churn in config.
Related errors
- target is not the managed symlink, use --force to remove it
- no dotfiles matched target filter: {}
- files: cannot unapply these entries:\n{}
- target is not the managed directory, use --force to remove i
- source directory is missing, so managed children cannot be i
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/ebf08aecb0ee1ae5.
Report an issue: GitHub.