Skip to content

feat(shim): add native .exe shim mode for Windows#8045

Merged
jdx merged 8 commits intomainfrom
feat/windows-exe-shims
Feb 7, 2026
Merged

feat(shim): add native .exe shim mode for Windows#8045
jdx merged 8 commits intomainfrom
feat/windows-exe-shims

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 7, 2026

Summary

  • Adds a mise-shim crate that produces a tiny native Windows executable (~50-80KB release). When copied as node.exe, python.exe, etc., it reads its own filename and invokes mise x -- <tool> <args>.
  • Makes "exe" the new default windows_shim_mode, replacing "file" (.cmd shims). This resolves intermittent ENOENT errors from spawnSync, control flow breakage in batch scripts, where.exe failures, and compatibility with package managers (bun, npm) expecting real .exe files on PATH.
  • Follows the pattern used by Scoop, Volta, Bun, and Chocolatey. Ref: Discussion #7998, community PoC at iki/mise-shim.

Changes

File Change
crates/mise-shim/Cargo.toml New - minimal crate, zero external dependencies
crates/mise-shim/src/main.rs New - shim binary (~35 lines)
Cargo.toml Add crates/mise-shim to workspace members
src/shims.rs Add "exe" mode to reshim(), add_shim(), get_desired_shims(); add find_mise_shim_bin() helper
settings.toml Change default to "exe", add docs for new mode
schema/mise.json Auto-regenerated from settings.toml
scripts/build-tarball.ps1 Build and package mise-shim.exe alongside mise.exe
e2e-win/shim.Tests.ps1 Add "exe" mode test
docs/faq.md Update VSCode EINVAL FAQ to reference new default

Test plan

  • cargo build -p mise-shim produces target/debug/mise-shim (verified on Linux)
  • cargo clippy --workspace passes clean
  • All pre-commit lints pass
  • On Windows: mise settings windows_shim_mode exe && mise reshim --force creates .exe shims
  • On Windows: shims execute tools correctly (e.g. node --version, python --version)
  • On Windows: where node resolves to the .exe shim
  • On Windows: e2e test shim.Tests.ps1 "run on exe" passes
  • Existing file, hardlink, and symlink modes 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-shim binary (std-only) that acts as a native Windows .exe shim by invoking mise x -- <tool> ... based on its own filename.

Switches Windows shimming to default to windows_shim_mode = "exe" and updates shim generation to copy mise-shim.exe as <tool>.exe (plus an extensionless bash shim), with a fallback to file mode if mise-shim.exe isn’t available; reshim now tracks shim mode via a .mode metadata 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 .exe shim mode.

Written by Cursor Bugbot for commit d96ca2d. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings February 7, 2026 16:42
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 mise's functionality on Windows by introducing a native executable shim mode. By making exe the default windows_shim_mode, it addresses critical compatibility and reliability issues previously encountered with .cmd file-based shims, ensuring smoother integration with Windows environments and various package managers. The change aligns mise with established practices used by other popular Windows tools.

Highlights

  • New Native Windows Shim Mode: Introduced a new mise-shim crate that produces a tiny native Windows executable. This mise-shim.exe acts as a proxy, reading its own filename (e.g., node.exe) and invoking mise x -- <tool> <args>.
  • Default Shim Mode Change: The default windows_shim_mode has been changed from "file" (which used .cmd shims) to "exe". This new default is recommended for improved compatibility and reliability.
  • Improved Windows Compatibility: This change resolves several long-standing issues on Windows, including intermittent ENOENT errors from spawnSync, control flow breakage in batch scripts, where.exe failures, and compatibility problems with package managers like Bun and npm that expect real .exe files on PATH.
  • Build and Documentation Updates: The build process (scripts/build-tarball.ps1) has been updated to compile and package mise-shim.exe. Documentation (docs/faq.md, settings.toml) and schema (schema/mise.json) have been updated to reflect the new exe mode and its benefits.
Changelog
  • Cargo.lock
    • Added mise-shim package entry.
  • Cargo.toml
    • Added crates/mise-shim to the workspace members.
  • crates/mise-shim/Cargo.toml
    • New file: Defines the mise-shim crate, a minimal Rust project with zero external dependencies for the native shim executable.
  • crates/mise-shim/src/main.rs
    • New file: Implements the core logic for mise-shim.exe. It determines the tool name from its own executable path and then executes mise x -- <tool> <args>.
  • docs/faq.md
    • Updated the VSCode EINVAL FAQ to suggest the new exe shim mode as a resolution for Node.js security fix related issues.
  • e2e-win/shim.Tests.ps1
    • Added a new test case to verify the functionality of the "exe" shim mode on Windows, ensuring shims execute tools correctly and where command resolves to the .exe shim.
  • schema/mise.json
    • Updated the windows_shim_mode schema to include "exe" as a valid option and set it as the new default value.
  • scripts/build-tarball.ps1
    • Modified to build mise-shim.exe and package it alongside mise.exe in the distribution tarball.
  • settings.toml
    • Changed the default value for windows_shim_mode to "exe".
    • Updated the documentation for windows_shim_mode to describe the new "exe" option and its benefits, recommending it as the default.
  • src/shims.rs
    • Modified the reshim function to include "exe" mode when determining if shims should be removed during a forced re-shim operation.
    • Added a new find_mise_shim_bin helper function to locate the mise-shim.exe binary, searching next to mise.exe first and then the system PATH.
    • Updated the add_shim function to implement the "exe" mode, which involves copying mise-shim.exe as <tool>.exe and creating an extensionless bash script for Git Bash/Cygwin.
    • Modified get_desired_shims to return both .exe and extensionless shims when windows_shim_mode is set to "exe".
Activity
  • The pull request description indicates that it was generated with Claude Code.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 default windows_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
Comment on lines +175 to +194
"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} "$@"
"#},
)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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() {
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
if candidate.exists() {
if candidate.is_file() {

Copilot uses AI. Check for mistakes.
Comment on lines +360 to +363
vec![
p.with_extension("exe").to_string_lossy().to_string(),
p.with_extension("").to_string_lossy().to_string(),
]
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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()]

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +23
.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)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
.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)

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +42
mise x go@1.23.3 -- where go
mise x go@1.23.3 -- go version | Should -BeLike "go version go1.23.3 windows/*"
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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>
@jdx jdx force-pushed the feat/windows-exe-shims branch from f13a7ab to bf92ca0 Compare February 7, 2026 16:45
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +175 to +201
"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)
)
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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")) {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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").

Suggested change
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>
@github-actions
Copy link

github-actions bot commented Feb 7, 2026

Hyperfine Performance

mise x -- echo

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%

jdx and others added 2 commits February 7, 2026 17:30
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>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON, but a Cloud Agent failed to start.

jdx and others added 3 commits February 7, 2026 18:09
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>
@jdx jdx merged commit ce63977 into main Feb 7, 2026
37 checks passed
@jdx jdx deleted the feat/windows-exe-shims branch February 7, 2026 19:31
mise-en-dev added a commit that referenced this pull request Feb 8, 2026
### 🚀 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)
@Delagen
Copy link

Delagen commented Feb 9, 2026

@jdx seems mise-shim.exe does not updated (or downloaded) during mise self-update, so i need to download and extract manually

@iki
Copy link
Contributor

iki commented Feb 9, 2026

@jdx seems mise-shim.exe does not updated (or downloaded) during mise self-update, so i need to download and extract manually

Confirming, as of v2026.2.8, mise doesn't yet download/update mise-shim.exe as a part of mise self-update, so you can download the latest release windows zip manually and place the shim next to mise.exe.

  • Also, if you used chocolatey to install mise, put the shim in %ProgramData%\chocolatey\lib\mise\<arch> instead of %ProgramData%\chocolatey\bin. However, it's better to install mise using winget install jdx.mise instead, as it uses symlinks and avoids double shim on each tool execution.

@cspotcode
Copy link
Contributor

cspotcode commented Feb 9, 2026

@iki I am seeing that mise gets confused by winget's symlinks and fails to locate mise-shim.exe. Are you hitting the same issue?

It attempts to find it in:
AppData\Local\Microsoft\WinGet\Links
not
AppData\Local\Microsoft\WinGet\Packages\jdx.mise_Microsoft.Winget.Source_8wekyb3d8bbwe\mise\bin

Reproduction:

> winget install mise
...
> mise --version
...
2026.2.8 windows-x64 (2026-02-09)
> mise reshim
mise WARN  mise-shim.exe not found next to C:\Users\cspot\AppData\Local\Microsoft\WinGet\Links\mise.exe or on PATH, falling back to "file" shim mode

To prove my winget has created symlinks:

> Get-Item C:\Users\cspot\AppData\Local\Microsoft\WinGet\Links\mise.exe

    Directory: C:\Users\cspot\AppData\Local\Microsoft\WinGet\Links

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
la---            2/9/2026  5:43 PM              0 mise.exe -> C:\Users\cspot\AppData\Local\Microsoft\WinGet\Packages\jdx.mise_Microsoft.Winget.Source_8wekyb3d8bbwe\mise\bin\mise.exe

jdx added a commit that referenced this pull request Feb 10, 2026
## 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>
@iki
Copy link
Contributor

iki commented Feb 10, 2026

@iki I am seeing that mise gets confused by winget's symlinks and fails to locate mise-shim.exe. Are you hitting the same issue?

It attempts to find it in: AppData\Local\Microsoft\WinGet\Links not AppData\Local\Microsoft\WinGet\Packages\jdx.mise_Microsoft.Winget.Source_8wekyb3d8bbwe\mise\bin

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, winget install jdx.mise does not yet create link for mise-shim.exe, it needs updated manifest, I'll create a PR there later, when 2026.2.9 will be merged.

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.

@cspotcode
Copy link
Contributor

cspotcode commented Feb 10, 2026

The missing symlink in WinGet\Links is what I was describing: mise.exe is found in WinGet\Links (the symlink pointing to Packages), so mise.exe's own logic attempts to find mise-shim.exe in \Links instead of Packages\ and can't.

If mise's resolution logic understood to resolve the mise.exe symlink to the Packages\ target, then it would successfully locate mise-shim.exe without a mise-shim.exe symlink in Links.

Two ways to solve the problem: either symlink Links\mise-shim.exe in the manifest, or tweak the search logic to follow the Links\mise.exe symlink.

iki added a commit to iki/winget-pkgs that referenced this pull request Feb 11, 2026
Add 'mise-shim.exe' as a nested installer file, it is available and required since 2026.2.7, see jdx/mise#8045
@iki
Copy link
Contributor

iki commented Feb 11, 2026

Btw, winget install jdx.mise does not yet create link for mise-shim.exe, it needs updated manifest, I'll create a PR there later, when 2026.2.9 will be merged.

EDIT: Added microsoft/winget-pkgs#338303 PR to fix 2026.2.8 and updated microsoft/winget-pkgs#338236 PR to add 2026.2.9

Both PR were merged, so any new winget mise installs (2026.2.8+) have now correct symlink to mise-shim.exe.

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):

  1. either resolve mise.exe symlink as descibed by @cspotcode above in feat(shim): add native .exe shim mode for Windows #8045 (comment)
  2. or make mise-shim.exe part of the self-update, which would be working, but ugly, as this would have new mise-shim.exe in Links directly and possibly older unused in Packages

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.

@jdx
Copy link
Owner Author

jdx commented Feb 11, 2026

I added the self-update fix that should add mise-shim.exe, so that should work for those users right?

@iki
Copy link
Contributor

iki commented Feb 12, 2026

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

@iki
Copy link
Contributor

iki commented Feb 13, 2026

I added the self-update fix that should add mise-shim.exe, so that should work for those users right?

@jdx tested ok ✅:

  • removed shim from Links and Packages
  • run self update 2026.2.10 to 2026.2.11
  • checked that self-update just put the shim exe to Links
  • tested mise reshim
>>> 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

lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 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)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
## 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>
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.

5 participants