{"record":{"id":"ebf08aecb0ee1ae5","repo":"jdx/mise","slug":"target-symlink-points-to-use-force-to-remove","errorCode":null,"errorMessage":"target symlink points to {}, use --force to remove it","messagePattern":"target symlink points to (.+?), use --force to remove it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/files.rs","lineNumber":1537,"sourceCode":"    let mut paths = IndexMap::<PathBuf, ()>::new();\n    let mut cleanup_empty_dirs = false;\n    let mut conditional = false;\n    let mut clear_symlink_each_state = false;\n    match req.mode {\n        // A Windows file link that came out as a copy is planned by content further down; one\n        // that is a real symlink belongs here, where the link target is what identifies it as\n        // ours. `plan_expected_content` requires a non-symlink, so routing a symlink there\n        // would make unapply demand `--force`.\n        FileMode::Symlink\n            if !(cfg!(windows) && req.source.is_file() && !req.target.is_symlink()) =>\n        {\n            if req.target.is_symlink() {\n                let dest = std::fs::read_link(&req.target)?;\n                if opts.force || dest == req.source || points_at_same_file(&req.target, &req.source)\n                {\n                    paths.insert(req.target.clone(), ());\n                } else {\n                    bail!(\n                        \"target symlink points to {}, use --force to remove it\",\n                        dest.display_user()\n                    );\n                }\n            } else if req.target.exists() {\n                if opts.force {\n                    paths.insert(req.target.clone(), ());\n                } else {\n                    bail!(\"target is not the managed symlink, use --force to remove it\");\n                }\n            }\n        }\n        FileMode::SymlinkEach if !cfg!(windows) => {\n            clear_symlink_each_state = symlink_each_state_path(req).exists();\n            if req.target.is_symlink() || (req.target.exists() && !req.target.is_dir()) {\n                if opts.force {\n                    paths.insert(req.target.clone(), ());\n                } else {","sourceCodeStart":1519,"sourceCodeEnd":1555,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/files.rs#L1519-L1555","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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"],"exampleFix":"# before: target points elsewhere than the configured source\n$ readlink ~/.vimrc\n/home/me/other/vimrc          # mise.toml says source = \"~/dotfiles/vimrc\"\n$ mise bootstrap dotfiles unapply\nError: target symlink points to /home/me/other/vimrc, use --force to remove it\n\n# after: source of truth is ~/dotfiles/vimrc -> re-apply first\n$ mise bootstrap dotfiles apply   # recreates the correct symlink\n$ mise bootstrap dotfiles unapply","handlingStrategy":"validation","validationCode":"# Pre-check: every symlink-mode entry's target must point at its source.\ncheck() {\n  src=$(realpath \"$1\"); tgt_link=$(readlink \"$2\" 2>/dev/null)\n  [ \"$tgt_link\" ] && [ \"$(realpath \"$2\")\" = \"$src\" ] || echo \"MISMATCH: $2 -> $tgt_link (want $src)\"\n}\ncheck ~/dotfiles/vimrc ~/.vimrc","typeGuard":"fn is_foreign_symlink(err: &miette::Report) -> bool {\n    err.to_string()\n        .starts_with(\"target symlink points to\")\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["mise","dotfiles","symlink","unapply","ownership"],"backgroundTag":"symlink-target-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T20:17:18.057Z"}