fix: handle locked .exe shims on Windows during reshim#8517
Conversation
On Windows, native `.exe` shims (introduced in v2026.2.7 via jdx#8045) get locked by processes or the shell when they are on PATH. When `reshim()` tries to `rm -rf` the entire shims directory, it fails with "Access is denied (os error 5)". This change: 1. Stops nuking the entire shims directory for exe/hardlink mode on every reshim. Instead, use the existing diff-based approach to only add/remove individual shims as needed. 2. When force or mode-change requires a full rebuild on Windows, removes shims individually instead of via `remove_dir_all`. 3. For locked `.exe` files that cannot be deleted, renames them to `.old` (Windows allows renaming locked files) so the path is freed for the new shim. Old files are cleaned up on the next reshim. Fixes the regression where `mise install` or `just setup` would fail on Windows CI (GitHub Actions) with: mise ERROR failed to rebuild shims mise ERROR failed rm -rf: C:\Users\...\mise\shims mise ERROR Access is denied. (os error 5) Bisected to v2026.2.7 which changed the default windows_shim_mode from "file" to "exe".
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request resolves a regression on Windows where Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively addresses a regression on Windows where reshim would fail due to locked .exe shims. The approach of individually removing shims and using a rename-fallback for locked files is a solid solution. The changes are well-contained and specific to Windows, preserving existing behavior on other platforms. I have a couple of suggestions to improve code clarity and error handling.
| } else if is_windows_hardlink_or_exe { | ||
| // For exe/hardlink mode we cannot rely on symlink staleness checks, so | ||
| // recompute diffs but only add/remove individual shims — never nuke the | ||
| // whole directory. | ||
| get_shim_diffs(config, &mise_bin, ts).await? | ||
| } else { | ||
| get_shim_diffs(config, &mise_bin, ts).await? | ||
| }; |
There was a problem hiding this comment.
The else if and else blocks here are identical, both calling get_shim_diffs. They can be combined into a single else block to simplify the code and remove duplication. The explanatory comment can be adapted for the unified block.
} else {
// For exe/hardlink mode on Windows, we cannot rely on symlink staleness checks, so
// we must recompute diffs. For other modes, this is also the correct behavior.
// This approach avoids nuking the whole directory, which is important for locked .exe shims.
get_shim_diffs(config, &mise_bin, ts).await?
};| let entries = match shims_dir.read_dir() { | ||
| Ok(entries) => entries, | ||
| Err(_) => return Ok(()), // directory doesn't exist yet | ||
| }; |
There was a problem hiding this comment.
The error handling for shims_dir.read_dir() is a bit too broad. Using Err(_) swallows all potential errors, including permission issues, treating them as if the directory simply doesn't exist. It would be more robust to specifically handle the NotFound error and propagate any other errors with more context.
let entries = match shims_dir.read_dir() {
Ok(entries) => entries,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()), // directory doesn't exist yet
Err(e) => return Err(e).wrap_err_with(|| format!("failed to read shims dir: {}", display_path(shims_dir))),
};
Greptile SummaryThis PR fixes a Windows regression (introduced in v2026.2.7) where Key changes:
Issues found:
Confidence Score: 3/5
|
- Collapse redundant else-if/else branches into single else arm - Remove unused is_windows_hardlink_or_exe variable - Only suppress NotFound in read_dir, propagate other errors - Handle ERROR_SHARING_VIOLATION (32) in addition to ACCESS_DENIED (5) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - Pin `goreleaser` to v2.14.1 in `test_aqua_github_attestations` e2e test instead of using `@latest` - goreleaser v2.14.2 (released 2026-03-08) has no GitHub artifact attestations published, causing the test to fail on both main and PRs (e.g. #8517) ## Test plan - [x] `mise run test:e2e test_aqua_github_attestations` passes locally 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only change that reduces flakiness by avoiding dependence on the latest upstream release; no production code paths are modified. > > **Overview** > Pins the `test_aqua_github_attestations` e2e script to install/uninstall `aqua:goreleaser/goreleaser@2.14.1` instead of `@latest`, and documents the rationale to avoid CI failures when new releases ship without GitHub artifact attestations. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7a8655b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
### 🐛 Bug Fixes - **(activate)** reorder shims to front of PATH on re-source in fish by @jdx in [#8534](#8534) - **(backend)** strip mise shims from dependency_env PATH to prevent fork bomb by @pose in [#8475](#8475) - **(github)** resolve "latest" version correctly via GitHub API by @jdx in [#8532](#8532) - **(lock)** set env tags and clarify lockfile docs by @jdx in [#8519](#8519) - **(lock)** use separate mise.<env>.lock files instead of env tags by @jdx in [#8523](#8523) - **(task)** include args in task output prefix and truncate long prefixes by @jdx in [#8533](#8533) - **(task)** only include args in task prefix when disambiguating duplicates by @jdx in [#8536](#8536) - **(test)** pin goreleaser version in attestation e2e test by @jdx in [#8518](#8518) - **(windows)** env._.source needs to run bash.exe on Windows (fix #6513) by @pjeby in [#8520](#8520) - handle locked .exe shims on Windows during reshim by @davireis in [#8517](#8517) ### 🚜 Refactor - **(prepare)** remove touch_outputs and update docs to reflect blake3 hashing by @jdx in [#8535](#8535) ### 📚 Documentation - **(docker)** replace jdxcode/mise image with curl install, update to debian:13-slim by @jdx in [#8526](#8526) - fix "gzip: stdin is encrypted" error in shell tricks cookbook by @pjeby in [#8512](#8512) ### 📦 Registry - add tigerbeetle ([github:tigerbeetle/tigerbeetle](https://github.com/tigerbeetle/tigerbeetle)) by @risu729 in [#8514](#8514) ### New Contributors - @pjeby made their first contribution in [#8520](#8520) - @davireis made their first contribution in [#8517](#8517) - @Aurorxa made their first contribution in [#8511](#8511) ## 📦 Aqua Registry Updates #### New Packages (6) - [`betterleaks/betterleaks`](https://github.com/betterleaks/betterleaks) - [`majorcontext/moat`](https://github.com/majorcontext/moat) - [`princjef/gomarkdoc`](https://github.com/princjef/gomarkdoc) - [`remko/age-plugin-se`](https://github.com/remko/age-plugin-se) - [`sudorandom/fauxrpc`](https://github.com/sudorandom/fauxrpc) - [`swanysimon/mdlint`](https://github.com/swanysimon/mdlint) #### Updated Packages (1) - [`moonrepo/moon`](https://github.com/moonrepo/moon)
Summary
Fixes a regression introduced in v2026.2.7 (#8045) where
mise reshimfails on Windows with:Root cause: In
exeshim mode,reshim()unconditionally callsremove_dir_allon the shims directory. Windows locks.exefiles while they're running, so if any shim-launched process is still alive, the delete fails.Bisected: v2026.2.6 works, v2026.2.7 breaks.
Reproduction
Workaround:
$env:MISE_WINDOWS_SHIM_MODE = "file"Changes
Stop nuking the entire shims directory for
exe/hardlinkmode on every reshim. Use the existing diff-based approach to only add/remove individual shims.When
forceor mode-change requires a full rebuild on Windows, remove shims individually instead of viaremove_dir_all.Rename-fallback for locked files: When an
.execan't be deleted (ERROR_ACCESS_DENIED), rename it to.old— Windows allows renaming locked files. Cleaned up on next reshim.Test plan
cargo checkpasseswindows-latestmise reshimwith locked.exeshims should succeed.oldfiles cleaned up on next reshim🤖 Generated with Claude Code