jdx/mise · error · eyre::Report

target is not the managed symlink, use --force to remove it

Error message

target is not the managed symlink, use --force to remove it

What it means

A `symlink`-mode entry's target exists but is a regular file or directory rather than a symlink, so mise has no ownership evidence (a real file may hold user data) and refuses removal unless `--force` is given.

Source

Thrown at src/system/files.rs:1546

        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 {
                    bail!("target is not the managed directory, use --force to remove it");
                }
            } else if req.target.is_dir() {
                for path in owned_links(req)? {
                    paths.insert(path, ());
                }
                cleanup_empty_dirs = true;
            }
        }

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Back up any wanted edits (`cp ~/.gitconfig ~/gitconfig.bak`), then re-run with `--force`
  2. Or restore mise ownership first: `mise bootstrap dotfiles apply` recreates the symlink, after which unapply verifies normally
  3. Merge hand edits back into the dotfiles source before forcing

Example fix

# before: target is a real file, not the managed symlink
$ ls -l ~/.gitconfig
-rw-r--r-- 1 me me 412 ~/.gitconfig
$ mise bootstrap dotfiles unapply
Error: target is not the managed symlink, use --force to remove it

# after: keep edits, then force-remove
$ cp ~/.gitconfig ~/gitconfig.bak && git -C ~/dotfiles diff  # salvage edits
$ mise bootstrap dotfiles unapply --force
Defensive patterns

Strategy: validation

Validate before calling

# Fail early when a symlink-mode target is not a symlink.
for t in ~/.gitconfig ~/.vimrc; do
  [ -e "$t" ] && [ ! -L "$t" ] && echo "NOT A SYMLINK: $t" && exit 1
done

Type guard

fn is_not_managed_symlink(err: &miette::Report) -> bool {
    err.to_string()
        .starts_with("target is not the managed symlink")
}

Prevention

When it happens

Trigger: `unapply` where the target exists (`req.target.exists()`) but `is_symlink()` is false — the link was replaced by a real file (some editors 'unlink and save'), or the entry previously ran in copy/content mode and the mode was switched to symlink without re-apply.

Common situations: Editor replaced the symlink with a copy of the file; user did `cp realfile ~/.gitconfig` over the link; mode churn in mise.toml without a re-apply in between.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/c24905f7b272248a. Report an issue: GitHub.