{"record":{"id":"2f2e01232c6278a7","repo":"jdx/mise","slug":"refusing-to-remove-non-link","errorCode":null,"errorMessage":"refusing to remove non-link: {}","messagePattern":"refusing to remove non-link: (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/file.rs","lineNumber":1210,"sourceCode":"    };\n    Ok(Some(resolve_relative_link_target(link, target)))\n}\n\nfn resolve_relative_link_target(link: &Path, target: PathBuf) -> PathBuf {\n    if target.is_absolute() {\n        target\n    } else {\n        link.parent().unwrap_or(link).join(target)\n    }\n}\n\n#[cfg(unix)]\npub fn remove_symlink_or_junction(link: &Path) -> Result<()> {\n    // POSIX has no standard unlink-by-handle operation, so a concurrent replacement can still\n    // occur between this check and remove_file. The latter cannot remove directories; callers\n    // must keep the parent directory protected from untrusted writers.\n    if dir_link_target(link)?.is_none() {\n        bail!(\"refusing to remove non-link: {}\", display_path(link));\n    }\n    fs::remove_file(link)\n        .wrap_err_with(|| format!(\"failed to remove symlink: {}\", display_path(link)))\n}\n\n#[cfg(windows)]\npub fn remove_symlink_or_junction(link: &Path) -> Result<()> {\n    let link_handle = open_link_for_removal(link)?;\n    remove_open_link(link_handle)\n        .wrap_err_with(|| format!(\"failed to remove link or junction: {}\", display_path(link)))\n}\n\n#[cfg(windows)]\nfn open_link_for_removal(link: &Path) -> Result<File> {\n    use std::os::windows::fs::OpenOptionsExt;\n    use std::os::windows::io::AsRawHandle;\n    use windows_sys::Win32::Storage::FileSystem::{\n        DELETE, FILE_ATTRIBUTE_TAG_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,","sourceCodeStart":1192,"sourceCodeEnd":1228,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/file.rs#L1192-L1228","documentation":"Unix implementation of `remove_symlink_or_junction` (src/file.rs:1201): before unlinking, `dir_link_target` must confirm the path actually is a symlink (to a directory). If it isn't — a regular file or a real directory sits at the path — mise refuses, because `fs::remove_file` must never delete real directory trees. The doc comment notes a POSIX race is still possible; callers must keep the parent dir protected from untrusted writers.","triggerScenarios":"mise replacing an install/version symlink (e.g. under `~/.local/share/mise/installs/<tool>/<version>` or a shim link) when that path has been replaced by a real file or directory — a user copied a real install over the link location, or a tool wrote through the link and replaced it.","commonSituations":"Users 'fixing' broken installs by `cp -r`-ing a real directory onto mise's symlink path; backups/restores that did not preserve symlink-ness; interrupted installs leaving directories where links belong; third-party version managers writing into the same paths.","solutions":["Inspect the path: `ls -la <path>` — determine whether a real file/directory now occupies a spot mise expects to own as a symlink","If the real directory is yours, move its contents to safety, then remove it manually (`rm -rf` only after verifying)","Reinstall the affected tool version so mise recreates its links: `mise install -f <tool>@<version>`","Stop placing real files/dirs on paths mise manages as symlinks; use mise aliases instead"],"exampleFix":"# before: installs/node/22 is a real dir, not a symlink\n$ mv ~/.local/share/mise/installs/node/22 /tmp/node-22-backup\n$ mise install -f node@22\n# after: mise recreates and owns the link/dir layout","handlingStrategy":"type-guard","validationCode":"# confirm the path is a symlink before letting mise replace it\n[ -L \"$path\" ] && echo link || { echo \"$path is a real file/dir — resolve manually\" >&2; exit 1; }","typeGuard":"fn is_removable_link(p: &std::path::Path) -> bool {\n    p.symlink_metadata()\n        .map(|m| m.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"Catch `refusing to remove non-link`, inspect the path with `ls -la`, and only after confirming the real file/dir is not mise-managed data, remove it manually and re-run the install command.","preventionTips":["Never copy real directories over mise's install paths; use `mise install`/`mise alias`","After restoring backups, check that mise-managed paths are still symlinks where expected","Keep third-party version managers from writing into ~/.local/share/mise"],"tags":["mise","filesystem","symlink","unix","install"],"backgroundTag":"unexpected-file-type","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}