Skip to content

fix(windows): remove dangling runtime symlink pointer files on uninstall - #11095

Merged
jdx merged 2 commits into
jdx:mainfrom
JamBalaya56562:fix-windows-runtime-symlink-cleanup
Jul 20, 2026
Merged

jdx merged 2 commits into
jdx:mainfrom
JamBalaya56562:fix-windows-runtime-symlink-cleanup

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • resolve a runtime symlink's stored target and check its existence when pruning dangling version pointers, instead of path.exists() on the pointer itself
  • add unit coverage for remove_missing_symlinks_in_dir (none existed)
  • add a Windows e2e test covering uninstall cleanup of pointer files

Context

Fixes #5260.

On Windows, runtime "symlinks" (the partial-version pointers like 16, 16.20, latest in installs\<tool>\) are regular files containing the relative target path (make_symlink_or_file). The cleanup in remove_missing_symlinks_in_dir gated removal on is_runtime_symlink(&path) && !path.exists() — on unix exists() follows the real symlink and turns false once the target is gone, but on Windows the pointer is an ordinary file that always exists, so stale pointers were never removed after mise uninstall. The leftover files also kept remove_dir_ignore from removing the now-empty tool directory.

The fix resolves the stored target via file::resolve_symlink and checks that against the installs dir, which is exactly what exists() computed on unix (runtime symlink targets are always ./-relative to the installs dir), so unix behavior is unchanged.

Reproduced on Windows 11 with v2026.7.7 in an isolated MISE_DATA_DIR:

> mise install yq@4.44.3 yq@4.45.4
> mise uninstall yq@4.44.3
# installs\yq afterwards still contains:
#   4.44  ->  .\4.44.3   (dangling pointer, target dir deleted)

Tests

  • cargo fmt --check
  • new unit tests: cargo test remove_missing_symlinks (fail on Windows before the fix, pass after; pass on unix both before and after, documenting parity)
  • new e2e-win/uninstall_symlink.Tests.ps1: installs two yq versions, uninstalls one, asserts the stale pointer is gone while the remaining version's pointers survive; then asserts the tool dir disappears when the last version is uninstalled

This PR was prepared by an AI coding assistant.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows uninstall cleanup for versioned tool installations.
    • Removed stale version pointers while preserving valid versions and the latest pointer.
    • Removed the tool’s installation directory when no versions remain.
  • Tests
    • Added end-to-end and unit coverage for dangling runtime pointers and cleanup behavior.

On Windows runtime symlinks are regular files containing the relative
target path, so the `!path.exists()` check in
remove_missing_symlinks_in_dir never fired and stale version pointer
files (e.g. `16`, `16.20`, `latest`) were left behind after
`mise uninstall`. Resolve the stored target and check its existence
instead, which on unix is equivalent to following the symlink.

Fixes jdx#5260

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Runtime symlink cleanup now resolves stored targets when identifying missing pointers. Unit tests and Windows end-to-end tests cover dangling pointer removal, preservation of valid versions, and deletion of the final tool directory.

Changes

Runtime symlink cleanup

Layer / File(s) Summary
Resolve dangling targets and validate cleanup
src/runtime_symlinks.rs
Missing runtime symlinks are detected by resolving their stored targets, with tests covering valid pointers, dangling pointers, and removal of empty install directories.
Verify Windows uninstall cleanup
e2e-win/uninstall_symlink.Tests.ps1
Windows tests verify stale version pointer removal, preservation of remaining pointers, and deletion of the tool directory after the final uninstall.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: jdx, risu729

Poem

A rabbit hops where symlinks sway,
Dangling pointers fade away.
Good versions stay beneath the moon,
Empty tool homes vanish soon.
“Hop!” says Bun, “the paths are right!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the Windows uninstall fix for dangling runtime symlink pointer files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Windows-only bug where partial-version pointer files (4.44, latest, etc.) were left behind after mise uninstall because the old check used path.exists() — which always returns true for a regular file — instead of checking whether the pointer's stored target directory still exists.

  • Core fix (src/runtime_symlinks.rs): extracts a runtime_symlink_target helper that reads the stored target via file::resolve_symlink, then remove_missing_symlinks_in_dir joins that target against installs_dir and calls exists() on the resolved path rather than on the pointer itself. On Unix the semantics are identical; on Windows this correctly detects dangling pointers.
  • Unit tests: two new #[cfg(test)] cases cover the dangling-pointer removal path and the "whole dir removed when only dangling pointers remain" path.
  • e2e test (e2e-win/uninstall_symlink.Tests.ps1): Pester test that installs two yq versions, uninstalls one, and asserts stale pointers are gone while surviving pointers and the latest link remain; a second block asserts the tool directory disappears when the last version is removed with --all.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped to the dangling-pointer removal path and on Unix it is a no-op semantic change.

The change replaces one well-understood existence check with an equivalent one that also works on Windows. On Unix, installs_dir.join(target).exists() resolves to the same path as the old path.exists() symlink-follow, so there is no regression risk on that platform. The extracted helper and new unit tests directly exercise the bug scenario.

No files require special attention.

Important Files Changed

Filename Overview
src/runtime_symlinks.rs Extracts runtime_symlink_target helper and uses it in remove_missing_symlinks_in_dir to resolve the stored target before checking existence, fixing the Windows dangling-pointer bug; adds two unit tests covering the new behaviour.
e2e-win/uninstall_symlink.Tests.ps1 New Pester e2e test for Windows uninstall cleanup; installs two yq versions, verifies stale pointers are removed and valid pointers retained, then verifies the tool directory is removed when the last version is gone.

Reviews (2): Last reviewed commit: "fix(windows): address review feedback on..." | Re-trigger Greptile

Comment thread src/runtime_symlinks.rs Resolved
Comment thread e2e-win/uninstall_symlink.Tests.ps1 Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e-win/uninstall_symlink.Tests.ps1`:
- Around line 21-23: Replace the non-ASCII em dash in the explanatory comment
about Windows version pointer files with a standard ASCII hyphen, leaving the
surrounding comment text and uninstall test behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 68915f23-70b9-47ec-80b9-03796c223214

📥 Commits

Reviewing files that changed from the base of the PR and between b46ddef and e3ecf6b.

📒 Files selected for processing (2)
  • e2e-win/uninstall_symlink.Tests.ps1
  • src/runtime_symlinks.rs

Comment thread e2e-win/uninstall_symlink.Tests.ps1 Resolved
- resolve the symlink target once per entry via a new
  runtime_symlink_target helper instead of resolving twice
  (is_runtime_symlink + resolve_symlink)
- make the last-version e2e test independent of the previous test by
  using `mise uninstall --all yq`
- replace a non-ASCII em-dash in the e2e script comment (PSScriptAnalyzer
  PSUseBOMForUnicodeEncodedFile)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jdx
jdx merged commit 98fc646 into jdx:main Jul 20, 2026
33 checks passed
@JamBalaya56562
JamBalaya56562 deleted the fix-windows-runtime-symlink-cleanup branch July 20, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants