feat(shim): add native .exe shim mode for Windows#8045
Conversation
Summary of ChangesHello @jdx, 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 significantly enhances Highlights
Changelog
Activity
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.
Pull request overview
Adds a new native Windows .exe shim mode backed by a tiny mise-shim binary, and makes it the default shim strategy on Windows to improve compatibility with where.exe, package managers, and spawn behavior.
Changes:
- Introduces
crates/mise-shim(a small native executable shim) and packages it in Windows tarballs. - Adds
"exe"support to shim generation and makes it the defaultwindows_shim_mode. - Updates schema/docs and adds an e2e Windows test case for
"exe"mode.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/shims.rs |
Implements "exe" shim mode and lookup for mise-shim.exe. |
settings.toml |
Switches default windows_shim_mode to "exe" and documents the new mode. |
scripts/build-tarball.ps1 |
Packages mise-shim.exe alongside mise.exe for distribution. |
schema/mise.json |
Regenerates schema to reflect updated default and description. |
e2e-win/shim.Tests.ps1 |
Adds a Windows e2e scenario for "exe" mode shims. |
docs/faq.md |
Updates VSCode EINVAL FAQ to reference the new default shim mode. |
crates/mise-shim/src/main.rs |
Adds the shim executable’s main logic (invokes mise x -- <tool> ...). |
crates/mise-shim/Cargo.toml |
Defines the new mise-shim crate metadata. |
Cargo.toml |
Adds crates/mise-shim to the workspace. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/shims.rs
Outdated
| "exe" => { | ||
| let mise_shim_bin = find_mise_shim_bin(mise_bin)?; | ||
| let shim_name = shim.trim_end_matches(".exe"); | ||
| // Copy mise-shim.exe as <tool>.exe | ||
| fs::copy(&mise_shim_bin, symlink_path.with_extension("exe")).wrap_err_with(|| { | ||
| eyre!( | ||
| "Failed to copy {} to {}", | ||
| display_path(&mise_shim_bin), | ||
| display_path(symlink_path) | ||
| ) | ||
| })?; | ||
| // Also create extensionless bash script for Git Bash/Cygwin | ||
| file::write( | ||
| symlink_path.with_extension(""), | ||
| formatdoc! {r#" | ||
| #!/bin/bash | ||
|
|
||
| exec mise x -- {shim_name} "$@" | ||
| "#}, | ||
| ) |
There was a problem hiding this comment.
The copy operation writes to symlink_path.with_extension("exe"), but the error message reports the destination as display_path(symlink_path), which can be misleading (especially when symlink_path is extensionless). Use the same computed destination path in both the actual copy and the error message.
src/shims.rs
Outdated
| // Look next to the mise binary first | ||
| if let Some(parent) = mise_bin.parent() { | ||
| let candidate = parent.join("mise-shim.exe"); | ||
| if candidate.exists() { |
There was a problem hiding this comment.
candidate.exists() will also return true for directories; returning a directory path here will make fs::copy fail later with a less-direct error. Prefer checking candidate.is_file() (or metadata + is_file) so the failure mode is accurate and earlier.
| if candidate.exists() { | |
| if candidate.is_file() { |
| vec![ | ||
| p.with_extension("exe").to_string_lossy().to_string(), | ||
| p.with_extension("").to_string_lossy().to_string(), | ||
| ] |
There was a problem hiding this comment.
In "exe" mode, add_shim already creates both <tool>.exe and the extensionless bash script. Returning both paths from get_desired_shims() likely causes add_shim to be invoked twice per tool, duplicating file I/O (two copies + two writes) and repeated find_mise_shim_bin() lookups. Consider returning only the .exe path for "exe" mode (and letting add_shim create the extensionless script as it already does), or alternatively adjust add_shim to create only the file corresponding to symlink_path.
| vec![ | |
| p.with_extension("exe").to_string_lossy().to_string(), | |
| p.with_extension("").to_string_lossy().to_string(), | |
| ] | |
| // In "exe" mode, add_shim already creates both <tool>.exe and the | |
| // extensionless shim script. We only need to track the .exe path here | |
| // to avoid invoking add_shim twice for the same tool. | |
| vec![p.with_extension("exe").to_string_lossy().to_string()] |
crates/mise-shim/src/main.rs
Outdated
| .to_string_lossy(); | ||
|
|
||
| let args: Vec<String> = env::args().skip(1).collect(); | ||
|
|
||
| let status = Command::new("mise") | ||
| .arg("x") | ||
| .arg("--") | ||
| .arg(tool.as_ref()) | ||
| .args(&args) |
There was a problem hiding this comment.
Using env::args() forces UTF-8 String arguments and can be lossy/non-representable on some platforms; Command is designed to work with OS-native strings. Prefer env::args_os().skip(1) and pass those directly to .args(...) (and consider keeping tool as an OsString/OsStr as well) to avoid any encoding edge cases.
| .to_string_lossy(); | |
| let args: Vec<String> = env::args().skip(1).collect(); | |
| let status = Command::new("mise") | |
| .arg("x") | |
| .arg("--") | |
| .arg(tool.as_ref()) | |
| .args(&args) | |
| .to_os_string(); | |
| let args = env::args_os().skip(1); | |
| let status = Command::new("mise") | |
| .arg("x") | |
| .arg("--") | |
| .arg(&tool) | |
| .args(args) |
e2e-win/shim.Tests.ps1
Outdated
| mise x go@1.23.3 -- where go | ||
| mise x go@1.23.3 -- go version | Should -BeLike "go version go1.23.3 windows/*" |
There was a problem hiding this comment.
mise x ... -- where go doesn’t assert anything, and PowerShell won’t fail the test automatically if where.exe returns a non-zero exit code. Capture and assert the output (e.g., that it contains the shim path and ends with go.exe) and/or assert $LASTEXITCODE -eq 0 so the test actually verifies the "exe" shim behavior it’s meant to cover.
Add a `mise-shim` crate that produces a tiny Windows executable. When copied as `node.exe`, `python.exe`, etc., it reads its own filename and invokes `mise x -- <tool> <args>`. This resolves intermittent ENOENT errors from spawnSync, control flow breakage in batch scripts, `where` failures, and compatibility issues with package managers expecting real .exe files on PATH. The new "exe" mode is now the default for `windows_shim_mode`, replacing "file" (.cmd shims). The approach follows the pattern used by Scoop, Volta, Bun, and Chocolatey. Based on the community PoC at https://github.com/iki/mise-shim Co-Authored-By: Jan Killian <jan.killian@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f13a7ab to
bf92ca0
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces a native .exe shim mode for Windows, which is a significant improvement for compatibility and resolves several outstanding issues. The implementation is well-structured, particularly the new mise-shim crate. My review focuses on a couple of areas in src/shims.rs where the logic can be made more efficient and correct. Specifically, the add_shim function for the new exe mode performs redundant file operations, and there's a minor redundancy in how the mise-shim.exe binary is located. I've provided suggestions to address these points.
| "exe" => { | ||
| let mise_shim_bin = find_mise_shim_bin(mise_bin)?; | ||
| let shim_name = shim.trim_end_matches(".exe"); | ||
| // Copy mise-shim.exe as <tool>.exe | ||
| fs::copy(&mise_shim_bin, symlink_path.with_extension("exe")).wrap_err_with(|| { | ||
| eyre!( | ||
| "Failed to copy {} to {}", | ||
| display_path(&mise_shim_bin), | ||
| display_path(symlink_path) | ||
| ) | ||
| })?; | ||
| // Also create extensionless bash script for Git Bash/Cygwin | ||
| file::write( | ||
| symlink_path.with_extension(""), | ||
| formatdoc! {r#" | ||
| #!/bin/bash | ||
|
|
||
| exec mise x -- {shim_name} "$@" | ||
| "#}, | ||
| ) | ||
| .wrap_err_with(|| { | ||
| eyre!( | ||
| "Failed to create shim script for {}", | ||
| display_path(symlink_path) | ||
| ) | ||
| }) | ||
| } |
There was a problem hiding this comment.
The current implementation for the "exe" shim mode is inefficient. It's called for each desired shim file (<tool>.exe and <tool>), but in both cases, it attempts to create both files. This results in redundant file operations. The function should only create the single file specified by its symlink_path argument, depending on whether it's the .exe or the extensionless script.
"exe" => {
if symlink_path.extension().and_then(|s| s.to_str()) == Some("exe") {
let mise_shim_bin = find_mise_shim_bin(mise_bin)?;
// Copy mise-shim.exe as <tool>.exe
fs::copy(&mise_shim_bin, symlink_path).wrap_err_with(|| {
eyre!(
"Failed to copy {} to {}",
display_path(&mise_shim_bin),
display_path(symlink_path)
)
})
} else {
let shim_name = symlink_path.file_name().unwrap().to_string_lossy();
// Also create extensionless bash script for Git Bash/Cygwin
file::write(
symlink_path,
formatdoc! {r#"
#!/bin/bash
exec mise x -- {shim_name} "$@"
"#},
)
.wrap_err_with(|| {
eyre!(
"Failed to create shim script for {}",
display_path(symlink_path)
)
})
}
}
src/shims.rs
Outdated
| } | ||
| } | ||
| // Fall back to searching PATH | ||
| if let Some(found) = file::which("mise-shim").or_else(|| file::which("mise-shim.exe")) { |
There was a problem hiding this comment.
The which crate, which file::which seems to wrap, automatically handles executable extensions like .exe on Windows. Therefore, calling file::which("mise-shim.exe") in the or_else is redundant. You can simplify this to a single call to file::which("mise-shim").
| if let Some(found) = file::which("mise-shim").or_else(|| file::which("mise-shim.exe")) { | |
| if let Some(found) = file::which("mise-shim") { |
- Use env::args_os() instead of env::args() for proper OsString handling
- Use is_file() instead of exists() when locating mise-shim.exe
- Remove redundant file::which("mise-shim.exe") fallback
- Make add_shim create only the file matching symlink_path extension
(.exe → copy binary, extensionless → write bash script)
- Assert where.exe output and exit code in e2e test
Co-Authored-By: Jan Killian <jan.killian@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.6 x -- echo |
21.3 ± 0.6 | 20.1 | 26.9 | 1.00 |
mise x -- echo |
21.8 ± 0.5 | 20.8 | 23.5 | 1.02 ± 0.04 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.6 env |
20.6 ± 0.3 | 19.7 | 21.8 | 1.00 |
mise env |
21.3 ± 2.0 | 20.0 | 59.1 | 1.03 ± 0.10 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.6 hook-env |
21.5 ± 0.4 | 20.7 | 24.1 | 1.00 |
mise hook-env |
22.1 ± 0.5 | 21.0 | 24.3 | 1.03 ± 0.03 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.6 ls |
19.4 ± 0.3 | 18.5 | 20.6 | 1.00 |
mise ls |
19.8 ± 0.4 | 18.9 | 21.6 | 1.02 ± 0.03 |
xtasks/test/perf
| Command | mise-2026.2.6 | mise | Variance |
|---|---|---|---|
| install (cached) | 113ms | 118ms | -4% |
| ls (cached) | 73ms | 72ms | +1% |
| bin-paths (cached) | 76ms | 77ms | -1% |
| task-ls (cached) | 546ms | 541ms | +0% |
file::which() does exact filename matching and does not append PATHEXT extensions, so "mise-shim" would never find "mise-shim.exe" on Windows. Co-Authored-By: Jan Killian <jan.killian@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ssing When windows_shim_mode is "exe" but mise-shim.exe is not found next to mise.exe or on PATH, fall back to "file" mode with a warning instead of failing. This prevents breakage in CI and existing installations that haven't been updated to include mise-shim.exe. Co-Authored-By: Jan Killian <jan.killian@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store the active shim mode in a .mode marker file inside the shims directory. On reshim, if the mode has changed (e.g. from "exe" to "symlink"), force-clear all shims so stale files from the previous mode don't persist. Co-Authored-By: Jan Killian <jan.killian@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
list_shims() includes files where extension() is None, which matches dotfiles like .mode. This caused the mode marker to be treated as a stale shim and deleted on every reshim, breaking mode change detection. Skip dotfiles in list_shims() since they are metadata, not shims. Co-Authored-By: Jan Killian <jan.killian@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
file::which on Windows only checks file extension, not existence, causing false positives when mise-shim.exe is not actually installed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The windows-e2e test for exe shim mode requires mise-shim.exe to be available next to mise.exe. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
### 🚀 Features - **(shim)** add native .exe shim mode for Windows by @jdx in [#8045](#8045) ### 🐛 Bug Fixes - **(install)** preserve config options and registry defaults by @jdx in [#8044](#8044) - **(link)** linked versions override lockfile during resolution by @jdx in [#8050](#8050) - **(release)** preserve aqua-registry sections in changelog across releases by @jdx in [#8047](#8047) - ls --all-sources shows duplicate entries by @roele in [#8042](#8042) ### 📚 Documentation - replace "inherit" terminology with config layering by @jdx in [#8046](#8046) ### 📦 Registry - switch oxlint to npm backend by default by @risu729 in [#8038](#8038) - add orval (npm:orval) by @zdunecki in [#8051](#8051) ### New Contributors - @zdunecki made their first contribution in [#8051](#8051)
|
@jdx seems mise-shim.exe does not updated (or downloaded) during |
Confirming, as of v2026.2.8, mise doesn't yet download/update
|
|
@iki I am seeing that mise gets confused by winget's symlinks and fails to locate It attempts to find it in: Reproduction: To prove my winget has created symlinks: |
## Summary - After `mise self-update` updates `mise.exe` on Windows, also extract and update `mise-shim.exe` from the same release archive - Non-fatal: if the shim update fails, a warning is emitted but the overall self-update still succeeds - Uses existing `zip`, `tempfile`, and `HTTP` client — no new dependencies Fixes the issue where `mise self-update` only updated `mise.exe` but left `mise-shim.exe` at the old version, requiring users to manually download it from GitHub releases. Reported after #8045 added native `.exe` shim mode. ## Test plan - [ ] Verify `cargo clippy --workspace` passes (confirmed locally) - [ ] Test on Windows: `mise self-update --force` updates both `mise.exe` and `mise-shim.exe` - [ ] Test on Windows: if `mise-shim.exe` is missing from the archive, a warning is printed but the update succeeds - [ ] Verify non-Windows builds are unaffected (all new code is `#[cfg(windows)]`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches the Windows self-update path and adds new download/extract/replace logic for an executable; failures are non-fatal but a bug could leave the shim outdated or not replaced as expected. > > **Overview** > On Windows, `mise self-update` now also updates `mise-shim.exe` after updating `mise.exe` by downloading the matching release `.zip`, verifying its `zipsign` signature, extracting `mise/bin/mise-shim.exe`, and atomically replacing the local shim (non-fatal on failure). > > This adds a Windows-only dependency on `zipsign-api` and updates `Cargo.lock` accordingly, plus a small tweak to version styling output to reuse the string value. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7267f26. 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 <noreply@anthropic.com>
Yes, however both mise self update to 2026.2.8 and 2026.2.9 worked correctly. The update reported mise.exe in Links, but writing to that file actually replaced the mise.exe in Packages and the link stayed untouched (and working). Btw, EDIT: Added microsoft/winget-pkgs#338303 PR to fix 2026.2.8 and updated microsoft/winget-pkgs#338236 PR to add 2026.2.9 Same for Chocolatey, I didn't find the manifest in source repo, but asked in package comments. |
|
The missing symlink in If mise's resolution logic understood to resolve the Two ways to solve the problem: either symlink |
Add 'mise-shim.exe' as a nested installer file, it is available and required since 2026.2.7, see jdx/mise#8045
Both PR were merged, so any new winget mise installs (2026.2.8+) have now correct symlink to Still, if anyone installed mise via winget before, they miss the symlink (until they update mise via winget), and mise will report errors as discussed by @cspotcode above in #8045 (comment). @jdx I see 2 possible solutions for older winget installs that are only updated via mise (possibly many):
Imo, 1 makes a good sense, and 2 can be added if needed. Regarding 2, I see one issue with mise-shim.exe, it crashes when run as mise-shim.exe. Let me know if you want me to fix it. In that case it would also need to be updated with self update. |
|
I added the self-update fix that should add mise-shim.exe, so that should work for those users right? |
@jdx Yes, placing mise-shim.exe in Links worked for me and winget keeps it there (until winget upgrade mise, which will replace it with correct symlink). I'll test the 2026.2.10 self-update with next mise release ... or if yo have any idea how to force self update on latest version |
@jdx tested ok ✅:
>>> fd -g mise-shim.exe "C:\Users\iki\AppData\Local\Microsoft\WinGet" -X rm
>>> where mise mise-shim.exe
C:\Users\iki\AppData\Local\Microsoft\WinGet\Links\mise.exe
>>> fd -gl mise*.exe "C:\Users\iki\AppData\Local\Microsoft\WinGet"
'C:\Users\iki\AppData\Local\Microsoft\WinGet\Links\mise.exe' -> /cygdrive/c/Users/iki/AppData/Loc
al/Microsoft/WinGet/Packages/jdx.mise_Microsoft.Winget.Source_8wekyb3d8bbwe/mise/bin/mise.exe
'C:\Users\iki\AppData\Local\Microsoft\WinGet\Packages\jdx.mise_Microsoft.Winget.Source_8wekyb3d8b
bwe\mise\bin\mise.exe'
>>> mise self-update -y
Checking target-arch... mise-v2026.2.11-windows-x64.zip
Checking current version... v2026.2.10
Looking for tag: v2026.2.11
mise.exe release status:
* Current exe: "C:\\Users\\iki\\AppData\\Local\\Microsoft\\WinGet\\Links\\mise.exe"
* New exe release: "mise-v2026.2.11-windows-x64.zip"
* New exe download url: "https://api.github.com/repos/jdx/mise/releases/assets/354901652"
The new release will be downloaded/extracted and the existing binary will be replaced.
Downloading...
[00:00:10] [========================================] 25.49 MiB/25.49 MiB (0s) Done
Verifying downloaded file...
Extracting archive... Done
Replacing binary file... Done
Updated mise to 2026.2.11
>>> where mise mise-shim.exe
C:\Users\iki\AppData\Local\Microsoft\WinGet\Links\mise.exe
C:\Users\iki\AppData\Local\Microsoft\WinGet\Links\mise-shim.exe
>>> fd -gl mise*.exe "C:\Users\iki\AppData\Local\Microsoft\WinGet"
'C:\Users\iki\AppData\Local\Microsoft\WinGet\Links\mise-shim.exe'
'C:\Users\iki\AppData\Local\Microsoft\WinGet\Links\mise.exe' -> /cygdrive/c/Users/iki/AppData/Loc
al/Microsoft/WinGet/Packages/jdx.mise_Microsoft.Winget.Source_8wekyb3d8bbwe/mise/bin/mise.exe
'C:\Users\iki\AppData\Local\Microsoft\WinGet\Packages\jdx.mise_Microsoft.Winget.Source_8wekyb3d8b
bwe\mise\bin\mise.exe'
>>> mise reshim |
### 🚀 Features - **(shim)** add native .exe shim mode for Windows by @jdx in [jdx#8045](jdx#8045) ### 🐛 Bug Fixes - **(install)** preserve config options and registry defaults by @jdx in [jdx#8044](jdx#8044) - **(link)** linked versions override lockfile during resolution by @jdx in [jdx#8050](jdx#8050) - **(release)** preserve aqua-registry sections in changelog across releases by @jdx in [jdx#8047](jdx#8047) - ls --all-sources shows duplicate entries by @roele in [jdx#8042](jdx#8042) ### 📚 Documentation - replace "inherit" terminology with config layering by @jdx in [jdx#8046](jdx#8046) ### 📦 Registry - switch oxlint to npm backend by default by @risu729 in [jdx#8038](jdx#8038) - add orval (npm:orval) by @zdunecki in [jdx#8051](jdx#8051) ### New Contributors - @zdunecki made their first contribution in [jdx#8051](jdx#8051)
## Summary - After `mise self-update` updates `mise.exe` on Windows, also extract and update `mise-shim.exe` from the same release archive - Non-fatal: if the shim update fails, a warning is emitted but the overall self-update still succeeds - Uses existing `zip`, `tempfile`, and `HTTP` client — no new dependencies Fixes the issue where `mise self-update` only updated `mise.exe` but left `mise-shim.exe` at the old version, requiring users to manually download it from GitHub releases. Reported after jdx#8045 added native `.exe` shim mode. ## Test plan - [ ] Verify `cargo clippy --workspace` passes (confirmed locally) - [ ] Test on Windows: `mise self-update --force` updates both `mise.exe` and `mise-shim.exe` - [ ] Test on Windows: if `mise-shim.exe` is missing from the archive, a warning is printed but the update succeeds - [ ] Verify non-Windows builds are unaffected (all new code is `#[cfg(windows)]`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches the Windows self-update path and adds new download/extract/replace logic for an executable; failures are non-fatal but a bug could leave the shim outdated or not replaced as expected. > > **Overview** > On Windows, `mise self-update` now also updates `mise-shim.exe` after updating `mise.exe` by downloading the matching release `.zip`, verifying its `zipsign` signature, extracting `mise/bin/mise-shim.exe`, and atomically replacing the local shim (non-fatal on failure). > > This adds a Windows-only dependency on `zipsign-api` and updates `Cargo.lock` accordingly, plus a small tweak to version styling output to reuse the string value. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7267f26. 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 <noreply@anthropic.com>
Summary
mise-shimcrate that produces a tiny native Windows executable (~50-80KB release). When copied asnode.exe,python.exe, etc., it reads its own filename and invokesmise x -- <tool> <args>."exe"the new defaultwindows_shim_mode, replacing"file"(.cmd shims). This resolves intermittent ENOENT errors fromspawnSync, control flow breakage in batch scripts,where.exefailures, and compatibility with package managers (bun, npm) expecting real.exefiles on PATH.Changes
crates/mise-shim/Cargo.tomlcrates/mise-shim/src/main.rsCargo.tomlcrates/mise-shimto workspace memberssrc/shims.rs"exe"mode toreshim(),add_shim(),get_desired_shims(); addfind_mise_shim_bin()helpersettings.toml"exe", add docs for new modeschema/mise.jsonscripts/build-tarball.ps1mise-shim.exealongsidemise.exee2e-win/shim.Tests.ps1"exe"mode testdocs/faq.mdTest plan
cargo build -p mise-shimproducestarget/debug/mise-shim(verified on Linux)cargo clippy --workspacepasses cleanmise settings windows_shim_mode exe && mise reshim --forcecreates.exeshimsnode --version,python --version)where noderesolves to the.exeshimshim.Tests.ps1"run on exe" passesfile,hardlink, andsymlinkmodes remain unaffected🤖 Generated with Claude Code
Note
Medium Risk
Changes Windows shim generation and defaults, which can affect command resolution and tool execution on Windows; fallback behavior reduces but doesn’t eliminate rollout risk.
Overview
Adds a new
crates/mise-shimbinary (std-only) that acts as a native Windows.exeshim by invokingmise x -- <tool> ...based on its own filename.Switches Windows shimming to default to
windows_shim_mode = "exe"and updates shim generation to copymise-shim.exeas<tool>.exe(plus an extensionless bash shim), with a fallback tofilemode ifmise-shim.exeisn’t available;reshimnow tracks shim mode via a.modemetadata file and forces shim dir regeneration when the mode changes.Updates CI/artifact packaging, Windows tarball build, schema/docs, and Windows e2e tests to build, ship, and validate the new
.exeshim mode.Written by Cursor Bugbot for commit d96ca2d. This will update automatically on new commits. Configure here.