Skip to content

Instantly share code, notes, and snippets.

@gabrielmoreira
Last active May 12, 2026 17:50
Show Gist options
  • Select an option

  • Save gabrielmoreira/6419a34e1dd5d90f861db59a701b8a1a to your computer and use it in GitHub Desktop.

Select an option

Save gabrielmoreira/6419a34e1dd5d90f861db59a701b8a1a to your computer and use it in GitHub Desktop.
Mise on Windows - Consolidated Field Report

Mise on Windows — Consolidated Field Report

About this report. This is a consolidation written by Claude (Opus 4.7 xhigh) at the user's request, distilling sessions from multiple coding agents (Claude, Codex, others) over ~1 month of intermittent investigation. The user has not line-by-line verified every claim — retroactive verification of incremental work spread across many sessions is itself impractical — but the reproductions, source-code references, and minimal repro scripts shipped alongside this document all run today. Treat this as a useful field reference for mise on Windows, not a definitive specification.

The user's broader goal, of which this report is one piece: a single, unified shell + tool-manager setup across Mac, Linux, and Windows. Mac and Linux unify naturally. Windows is the awkward one. Going all-in on WSL would be the easy unification path, but it is not practical here — WSL consumes system resources that the user prefers to dedicate to other programs, and modern Windows tooling has closed enough of the Unix gap that WSL isn't worth the dedicated overhead for daily work. The daily-driver shell is pwsh (7.x), which works extremely well for most things; Git Bash is the better-than-Cygwin alternative when POSIX bash is genuinely needed; WSL is reserved for the rare task that truly demands Linux. Within that frame, this report is the audit trail of every Windows-specific paper-cut that surfaced.


Author: Gabriel (PC-GABRIEL, Windows 11 Pro 26200)
Period: 2026-04-10 → 2026-05-12 (~1 month of intermittent investigation across multiple shell sessions)
Audience: the jdx/mise maintainers, and any Windows user trying to standardize on mise across cmd, pwsh/Windows PowerShell, Git Bash, and (lightly) WSL.
Scope: what fails, why it fails, minimal reproductions, workarounds applied locally, and proposed upstream fixes.
Security note: API keys / tokens that were observed in this user's environment have been redacted with <REDACTED>. Paths and tool versions are unredacted.


0. TL;DR

mise works beautifully on Windows when the entire chain has the right environment variables. The problem is that the chain is much more fragile than on Unix because:

  1. Windows clean-environment subprocesses are common (UI tools like Claude Desktop, VS Code task runners, JetBrains run configs, GitHub Actions runners). When they spawn mise's shims, they strip env vars that Unix never strips, and mise has nothing to fall back to.
  2. MISE_INSTALLS_DIR cannot be persisted outside an env var. If the user moves installs to C:\mise to avoid MAX_PATH, that knowledge is lost the moment env propagation is broken.
  3. mise-shim.exe is a 37-line wrapper that only resolves mise through PATH, with no fallback when PATH doesn't have it.
  4. mise auto-prefers aube when it sees aube installed anywhere on disk, even if aube isn't a member of the active toolset — instantly breaking every mise install npm:* until the user finds the setting to override it.
  5. mise's failure-cleanup is brittle on Windows — when an npm install fails, mise tries to rm -rf the partial install dir; Defender/file-locks turn that into a no-op; the next retry sees half-extracted node_modules and cascades further.

The user solved each of these in isolation but it took a month of cross-session detective work. This report consolidates the evidence so others don't have to repeat it.


1. Environment summary

Item Value
OS Windows 11 Pro 10.0.26200 (Build 26200)
Shells in scope cmd.exe, pwsh.exe (PowerShell 7.6), powershell.exe (5.1), Git Bash (bash.exe from Git for Windows), WSL bash
Default shell pwsh.exe (7.6)
mise installed WinGet (jdx.mise) at C:\Users\Gabriel\AppData\Local\Microsoft\WinGet\Packages\jdx.mise_…\mise\bin\mise.exe
mise version observed 2026.4.27 → 2026.5.0
MISE_INSTALLS_DIR C:\mise (set in HKCU and HKLM)
MISE_SHIMS_DIR default (%LOCALAPPDATA%\mise\shims)
Tools in config.toml ~50 (mix of aqua:, core:, github:, http:, npm: backends)
WSL distro Arch (runs as root by default; separate mise install at /root/.local/share/mise/installs/…)
clink not installed
starship installed via mise (C:\mise\starship\1.25.x\)
Diagnostic script C:\Users\Gabriel\windows-shell-doctor.ps1 (49KB read-only doctor; produced windows-shell-report.txt)

Why C:\mise? When a tool installs under the default %LOCALAPPDATA%\mise\installs\<tool>\<ver>\… (≈70 char prefix), npm-backed packages with deep node_modules trees (e.g. gsd-pi's 319-package install of sharp, playwright, @aws-sdk/*) reach absolute paths >260 chars and trigger MAX_PATH failures during postinstall or cleanup. Moving the installs root to C:\mise (7 chars vs ~70) reduces this prefix to ~40 chars and observably eliminates the MAX_PATH-class failures the user was hitting. Whether the actual breakage is from path length per se, deep node_modules, or PATH env var size compounding is not fully isolated, but moving to C:\mise made the problem go away.

mise's windows-shell-report.txt produced by this user's doctor confirms:

PATH length for process PATH : 6302
PATH entries for process PATH : 136
[WARN] mise: Observed very long install-tree path (252 chars).
[WARN] mise: Found node_modules content in installs tree.
[WARN] windows path strategy: Current process PATH is very large. cmd and npm lifecycle subprocesses may fail.

2. Issue catalog

# Title Class Severity Reproducible? Fix owner
1 mise-shim chain fails in clean envs without PATHEXT + SystemRoot + MISE_INSTALLS_DIR mise core High ✅ deterministic mise
2 npm.package_manager = "Auto" selects aube even when not pinned in toolset mise core High ✅ deterministic mise
3 mise install npm:* leaves stale install dir on failure, blocking retry mise core Medium ✅ when AV locks files mise
4 MISE_INSTALLS_DIR has no settings.toml equivalent mise core Medium ✅ by inspection of source mise
5 mise-shim.exe only resolves mise via PATH (no fallback) mise core Medium ✅ by inspection of source mise
6 sharp@0.34.5 postinstall fails under parallel mise install (cmd.exe loses node) 3rd-party + race High when triggered ⚠️ intermittent npm/Windows/AV interaction
7 npm tar extraction races: Cannot cd into … warnings under mise install npm + Windows AV Medium ⚠️ intermittent user (serialize + long-paths)
8 Long install-tree paths approach MAX_PATH Windows + npm High (silent) ✅ when default installs_dir used user (workaround)
9 Stale, version-pinned mise\installs\<tool>\<ver> entries in HKCU Path residue from past tooling Medium ✅ present in registry user (cleanup)
10 mise shims not in persistent Path (only injected by activate) mise install default Medium ✅ via mise doctor user (add to Path)
11 WSL imports Windows PATH + runs as root + reads /mnt/c config.toml WSL default + setup High (security/functional) ✅ deterministic user (wsl.conf)
12 Prompt fragmentation across 6 shells; no unified shell identity observability Low functional n/a user (opinion: Starship + clink)

Sections 3–14 below cover each row in detail.


3. Issue #1 — mise-shim chain fails in clean envs

Symptom

Spawning mise-shimmed binaries (e.g. npx) from a process that scrubs the environment block (most UI tools do, by design) fails. The visible errors vary:

Env passed to shim Failure mode
PATH + USERPROFILE only mise-shim hangs (WaitForExit(5000) times out) trying to resolve mise
+ MISE_INSTALLS_DIR=C:\mise mise x runs, but which::which_in in src/cli/exec.rs:387 cannot resolve npxnpx.cmd and exits with cannot find binary path. If it does spawn node, Node panics: Assertion failed: ncrypto::CSPRNG(nullptr, 0)
+ MISE_INSTALLS_DIR + PATHEXT + SystemRoot ✅ works

Original discovery

Claude Desktop on Windows would not start an MCP server whose command was npx. The MCP transport closed within 200ms of connection. The Claude logs showed mise ERROR cannot find binary path followed by transport shutdown. The user's claude_desktop_config.json had no "env" block at first; adding env entries one-by-one revealed the three minimum needed vars.

Minimal reproduction (PowerShell, no MCP/Claude needed)

Saved at C:\tmp\repro-mise-issue.ps1:

function Test-Env($extraEnv) {
  $psi = New-Object System.Diagnostics.ProcessStartInfo
  $psi.FileName = "cmd.exe"
  $psi.Arguments = '/c npx --version 2>&1'
  $psi.UseShellExecute = $false
  $psi.RedirectStandardOutput = $true
  $psi.RedirectStandardError = $true
  $psi.Environment.Clear()
  $psi.Environment["PATH"] = (
    "C:\WINDOWS\system32;C:\WINDOWS;" +
    "C:\Users\$env:USERNAME\AppData\Local\Microsoft\WinGet\Packages\" +
    "jdx.mise_Microsoft.Winget.Source_8wekyb3d8bbwe\mise\bin;" +
    "C:\Users\$env:USERNAME\AppData\Local\mise\shims"
  )
  $psi.Environment["USERPROFILE"] = "C:\Users\$env:USERNAME"
  foreach ($k in $extraEnv.Keys) { $psi.Environment[$k] = $extraEnv[$k] }
  $proc = [System.Diagnostics.Process]::Start($psi)
  if (!$proc.WaitForExit(5000)) { $proc.Kill(); return @{ exit=-1; out="TIMEOUT" } }
  @{ exit = $proc.ExitCode; out = ($proc.StandardOutput.ReadToEnd() + $proc.StandardError.ReadToEnd()) }
}

"=== 1. Minimal env (PATH + USERPROFILE only) ==="
Test-Env @{} | Out-Host

"=== 2. + MISE_INSTALLS_DIR (still broken) ==="
Test-Env @{ "MISE_INSTALLS_DIR"="C:\mise" } | Out-Host

"=== 3. + PATHEXT + SystemRoot (works!) ==="
Test-Env @{
  "MISE_INSTALLS_DIR"="C:\mise"
  "PATHEXT"=".COM;.EXE;.BAT;.CMD"
  "SystemRoot"="C:\WINDOWS"
} | Out-Host

Root cause — three layers in series

Source inspection of the local jdx/mise checkout (C:\Users\Gabriel\Projects\OSS\mise):

  1. MISE_INSTALLS_DIR is env-only. src/env.rs lines 122-129:

    pub static MISE_INSTALLS_DIR: Lazy<PathBuf> = Lazy::new(||
      var_path("MISE_INSTALLS_DIR").unwrap_or_else(|| MISE_DATA_DIR.join("installs")));

    No settings.toml reader. Same pattern for MISE_DATA_DIR, MISE_SHIMS_DIR. If the env var is dropped by a UI tool, mise falls back to %LOCALAPPDATA%\mise\installs and finds nothing.

  2. mise x resolves .cmd via the Rust which crate. src/cli/exec.rs:387:

    let program = which::which_in(program, lookup_path, cwd)?;

    On Windows, which::which_in enumerates PATHEXT extensions. With no PATHEXT, the .cmd extension is invisible, so npxC:\mise\node\26.x\npx.cmd is unresolvable. Error: cannot find binary path.

  3. Node.js needs SystemRoot for BCryptOpenAlgorithmProvider / CSPRNG seeding. Without it, the process panics on startup before user code runs.

Caveat — if shims were .cmd/.bat instead of .exe

In this user's setup the shims are .exe (mise-shim.exe copied per tool, see crates/mise-shim/src/main.rs), so the shim itself launches without PATHEXT. The PATHEXT requirement bites inside mise x. But under windows_shim_mode = "file" (or for tools where .exe shims are unavailable), mise generates .cmd shims. Without PATHEXT, Windows can't even resolve the shim — it fails earlier with 'npx' is not recognized before mise enters the picture.

Workaround (verified)

For every UI tool that supports an env block, pass:

{
  "env": {
    "MISE_INSTALLS_DIR": "C:\\mise",
    "PATHEXT": ".COM;.EXE;.BAT;.CMD",
    "SystemRoot": "C:\\WINDOWS"
  }
}

Concrete example used in C:\Users\Gabriel\AppData\Roaming\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "whimsical-desktop": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:21190/mcp"],
      "env": {
        "MISE_INSTALLS_DIR": "C:\\mise",
        "PATHEXT": ".COM;.EXE;.BAT;.CMD",
        "SystemRoot": "C:\\WINDOWS"
      }
    }
  }
}

Proposed upstream fixes

  1. Persist installs_dir (and data_dir, shims_dir) in ~/.config/mise/settings.toml. The env var can stay as override; the file is the persistent source. The shim binary would read this file (it already knows where home is).
  2. Inject Windows essentials in mise x's child env when missing. The shim is already wrapping a subprocess; before spawning, if PATHEXT / SystemRoot / COMSPEC are missing, populate them from the actually-installed Windows defaults.
  3. Document the requirement. Add a "Running mise shims from non-shell processes (UI tools, CI)" section to docs/dev-tools/shims.md listing the three env vars.

4. Issue #2 — aube is auto-selected when installed-but-not-pinned

Symptom

After installing aube (an alternative npm package manager that mise supports), every mise install npm:<anything> fails with:

DEBUG $ C:\Users\Gabriel\AppData\Local\mise\shims\aube.exe add --global <pkg>
mise ERROR cannot find binary path
ERROR C:\Users\Gabriel\AppData\Local\mise\shims\aube.exe failed

Even mise x -- aube --version fails (cannot find binary path), because the shim invokes mise x against a toolset that doesn't list aube.

Discovery context

On 2026-05-12 at 13:31 the user installed aube (probably while experimenting). From then on, every mise install npm:* started failing — not because of npm, but because mise pre-empted with aube. This was masked initially because the original MISE_INSTALLS_DIR discussion was ongoing.

Root cause

src/backend/npm.rs:609-622:

async fn package_manager_for_install(&self, ...) -> NpmPackageManager {
  let settings = Settings::get();
  match settings.npm.package_manager {
    NpmPackageManager::Auto if self.aube_is_installed(config, ts).await =>
      NpmPackageManager::Aube,
    NpmPackageManager::Auto => NpmPackageManager::Npm,
    package_manager => package_manager,
  }
}

aube_is_installed checks whether aube resolves to any path; it does not verify that aube is a member of the toolset that mise x will rebuild for the install context. So an orphan aube install (= present on disk in MISE_INSTALLS_DIR, but not declared in any active config.toml) causes AutoAube, then mise x -- aube cannot resolve because the toolset doesn't know aube.

Aggravating: src/backend/npm.rs:101:

fn get_optional_dependencies(&self) -> eyre::Result<Vec<&str>> {
  Ok(vec!["aube"])
}

aube is declared as an optional dependency of every npm: install, encouraging users to install it.

Minimal reproduction

mise install aube@latest        # installs to C:\mise\aube\1.1.0\
# Do NOT add `aube = "latest"` to ~/.config/mise/config.toml
mise install 'npm:cline@latest' --force    # fails with `cannot find binary path`
mise x -- aube --version                   # also fails

Workaround

Either:

  • mise settings set npm.package_manager npm (writes [settings.npm] package_manager = "npm" to ~/.config/mise/config.toml), or
  • Pin aube = "latest" in [tools], or
  • mise uninstall aube if not wanted.

Proposed upstream fix

aube_is_installed should only return true when aube is a member of the active toolset (i.e. resolvable through Toolset::which, not just present on disk). One-line fix in the same function.


5. Issue #3 — mise install leaves stale state when interrupted

Symptom

A failed mise install npm:gsd-pi@2.82.0:

WARN  failed to remove C:\mise\npm-gsd-pi\2.82.0: failed rm -rf: ...

Next retry produces cascade ENOENT during npm tar extraction:

npm warn tar ENOENT: Cannot cd into 'C:/mise/npm-gsd-pi/2.82.0/node_modules/gsd-pi/node_modules/sql.js'
npm warn tar ENOENT: Cannot cd into 'C:/mise/npm-gsd-pi/2.82.0/node_modules/gsd-pi/node_modules/file-type'
…

Eventually the install may succeed (with warnings) or may produce a new failure mode like Cannot find module 'scripts/install.js' because the partial extract has scripts/ files missing.

Why cleanup fails

The same npm log shows the source — Defender / file-locks during the failed install left handles open:

warn cleanup [Error: EPERM: operation not permitted, rmdir
  'C:\mise\npm-gsd-pi\2.82.0\node_modules\gsd-pi\…\node_modules\openai\resources\admin']

The Rust rm -rf call hits the same locks moments later and fails. mise logs WARN failed to remove … but treats the failure as non-fatal — leaving a partial tree that the next --force invocation extracts on top of.

Workaround

Explicit cleanup before retry:

Remove-Item -Recurse -Force C:\mise\npm-<tool>\<ver>
mise install 'npm:<tool>@<ver>' --force

Helper saved at C:\tmp\mise-windows-findings.md:

function mise-reinstall-npm {
  param([string]$Pkg)
  $name = ($Pkg -replace 'npm:', '' -replace '@.*', '' -replace '/', '-' -replace '@', '')
  Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "C:\mise\npm-$name"
  mise install $Pkg --force
}

Proposed upstream fix

  • Retry the rm -rf with rename-out-of-the-way (similar to what shims.rs::remove_shim_with_rename_fallback already does for locked .exe shims).
  • Promote the WARN to an ERROR when cleanup fails (so the user knows the next retry is unsafe).
  • Refuse to start --force install on top of a partial tree without confirming cleanup succeeded.

6. Issue #4 — MISE_INSTALLS_DIR is env-only (no config-file equivalent)

Symptom

The user persists MISE_INSTALLS_DIR=C:\mise to HKCU and HKLM. UI tools that drop the env var still cannot find the user's tools, even though the choice was made deliberately and could in principle be discovered via ~/.config/mise/.

Source

src/env.rs:122:

pub static MISE_INSTALLS_DIR: Lazy<PathBuf> = Lazy::new(||
  var_path("MISE_INSTALLS_DIR").unwrap_or_else(|| MISE_DATA_DIR.join("installs")));

There's no Settings::installs_dir field. By contrast windows_shim_mode, npm.package_manager, not_found_auto_install are all settings.

Why this matters

A persistable installs_dir setting would mean:

  • Adding MISE_INSTALLS_DIR to every UI tool's env block becomes unnecessary.
  • New machines configured by following a setup script don't depend on the user remembering to set env vars in the right scope.
  • WSL and Windows installs could read the same settings file and produce coherent state.

Proposed upstream fix

Add [settings] installs_dir = "C:\\mise" (and data_dir, shims_dir, cache_dir) support. Env var continues to override; file is the persistent default.


7. Issue #5 — mise-shim.exe has no fallback mise resolution

Source

crates/mise-shim/src/main.rs is 37 lines:

use std::env;
use std::process::{Command, exit};
fn main() {
    let exe = env::current_exe().unwrap_or_else(|e| {});
    let tool = exe.file_stem();
    let args = env::args_os().skip(1);
    let status = Command::new("mise")    // ← only looks up via PATH
        .arg("x").arg("--").arg(&tool)
        .args(args).status();
    match status {
        Ok(s) => exit(s.code().unwrap_or(1)),
        Err(e) => { eprintln!("mise-shim: failed to execute mise: {e}");exit(1); }
    }
}

Why this matters

When a UI tool spawns npx.exe (which is mise-shim.exe), the resulting Command::new("mise") resolves only against the inherited PATH. If the inherited PATH doesn't have mise\bin, the shim emits a generic failed to execute mise: <error>. This is observable in Claude Desktop's logs as "transport closed unexpectedly".

Proposed upstream fix

Before falling back to PATH:

  1. Look next to the shim itself (<shim_dir>\..\..\..\..\bin\mise.exe or any nearby path).
  2. Read a marker file written by mise reshim that records the absolute path of the mise.exe that generated the shims.
  3. Optionally also re-emit MISE_INSTALLS_DIR / MISE_DATA_DIR from that marker, so the eventual mise x doesn't depend on env propagation.

8. Issue #6 — sharp@0.34.5 postinstall fails under parallel mise install

Original event (2026-05-12 ~13:26 local / 11:26 UTC)

The user ran mise install which spawned 4 parallel npm install --global processes (one per npm: tool that needed an update). The npm cache logs:

Log file Tool Result
…11_26_51_673Z-debug-0.log @qwen-code/qwen-code ✅ exit 0
…11_26_51_722Z-debug-0.log cline ❌ exit 1
…11_26_52_047Z-debug-0.log gsd-pi ❌ exit 1
…11_26_53_381Z-debug-0.log vite-plus ✅ exit 0

Both failures had the same shape:

npm error path C:\mise\npm-<tool>\<ver>\node_modules\<tool>\node_modules\sharp
npm error command failed
npm error command C:\WINDOWS\system32\cmd.exe /d /s /c node install/check.js || npm run build
npm error 'node' is not recognized as an internal or external command,
npm error operable program or batch file.
npm error 'npm' is not recognized as an internal or external command,
npm error operable program or batch file.

So the cmd.exe spawned by npm for sharp@0.34.5's postinstall lost its node/npm PATH. The other two parallel installs (no sharp dependency) succeeded.

Why this is hard to attribute

Possible contributors, none definitively isolated:

  • PATH propagation race: 4 npm processes each spawning cmd.exe simultaneously; some hypothetical Windows env-block staleness during high contention.
  • Defender file-locks: same log shows EPERM during cleanup, evidence that AV was touching freshly extracted files.
  • node 26.0.0 vs 26.1.0: the failure was on 26.0.0; the user can no longer reproduce on 26.1.0.
  • PATH length (6302 chars on this machine, 136 entries) approaching some limit that emerges only under load.

Current state

Not reproducible after node 26.0.0 → 26.1.0 update and after clearing aube. Re-running mise install npm:gsd-pi@2.82.0 --force today works (with npm warn tar ENOENT: Cannot cd into … warnings — see Issue #7).

Mitigations recommended (proactive first)

  • Serialize installs for npm-heavy operations: MISE_JOBS=1 mise install …. Cuts the parallel filesystem contention that compounds with AV scanning, and removes the "two installs racing through sharp's postinstall at the same time" pattern that produced the original failure.
  • Clean state before retry when an install fails: Remove-Item -Recurse -Force C:\mise\npm-<tool>\<ver> then re-run with --force. The mise-reinstall-npm helper in §15.4 automates this.
  • Pin transitive sharp to a known-good version when possible, or report upstream to gsd-pi / cline maintainers so they prefer node-prebuild downloads over a local build.
  • AV exclusion of C:\mise is not the recommended primary mitigation — the install root receives third-party tarballs from npm registries, GitHub releases, aqua, and others, and real-time scanning is exactly the layer that catches a compromised release at unpack time. If a corporate policy environment explicitly permits a scoped, time-bound exclusion, that does shrink the scan-vs-extract race, but treat it as a trade-off and re-evaluate periodically — not as setup.

9. Issue #7 — npm tar ENOENT: Cannot cd into ... during extract

Symptom

Even on successful runs, mise install npm:* for heavyweight tools produces:

npm warn tar ENOENT: Cannot cd into 'C:/mise/npm-gsd-pi/2.82.0/node_modules/gsd-pi/node_modules/sql.js'
npm warn tar ENOENT: Cannot cd into 'C:/mise/npm-gsd-pi/2.82.0/node_modules/gsd-pi/node_modules/file-type'
…

The install eventually succeeds (with warnings) because npm retries.

Cause

Filesystem race during parallel extract. Defender real-time scan opens just-extracted files for inspection; npm's tar tries to cd into a directory still being scanned and gets ENOENT (or EPERM during the extract→scan→delete sequence).

Mitigation (proactive first)

  1. Serialize: MISE_JOBS=1 (or mise install --jobs 1) for npm-heavy tools. Removes most of the race.
  2. Enable Windows long-path support at the OS level so npm/Node can use \\?\-prefixed paths and avoid the MAX_PATH cliffs that compound with the AV race (GPO: Computer Configuration → Administrative Templates → System → Filesystem → Enable Win32 long paths; or registry HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1; reboot).
  3. Use a fast local SSD (ideally NVMe) for %LOCALAPPDATA%\npm-cache and C:\mise. Slow disks dramatically widen the scan-vs-extract window.
  4. Retry cleanup on EPERM/EBUSY when a previous install left partial state. Mise itself does not yet retry these (Issue #3); until it does, the mise-reinstall-npm helper in §15.4 cleans + retries from the user side.

An AV scan exclusion on C:\mise does shrink the race window, but we deliberately do not recommend it as a default: the install root holds tarballs and binaries downloaded from npm registries, GitHub releases, aqua, and other third parties, and real-time scanning is exactly the layer that catches a compromised release at unpack time. Treat any such exclusion as a scoped, time-bound trade-off — not as setup.


10. Issue #8 — long install-tree paths approach MAX_PATH

Evidence

From the May 9 windows-shell-report.txt:

[WARN] mise: Observed very long install-tree path (252 chars). Windows tooling may become fragile.
[WARN] mise: Found node_modules content in installs tree.
[WARN] windows path strategy: Current process PATH is very large.

Why MISE_INSTALLS_DIR=C:\mise is necessary (not preference)

Comparing prefixes when npm install --prefix <dir> produces deep node_modules:

installs_dir Prefix length Headroom for node_modules chain
%LOCALAPPDATA%\mise\installs\npm-<tool>\<ver>\ ~70 chars ~190 chars before MAX_PATH
C:\mise\npm-<tool>\<ver>\ ~40 chars ~220 chars before MAX_PATH

A 30-char headroom difference is often the difference between "works" and "ENOENT" for npm packages with deep transitive deps. Whether the exact failure mode is "absolute path > 260 chars" or "PATH env var becomes too long when activate prepends many entries" or both, the effect was that C:\mise made the failures stop for this user.

Recommendation

  • Use MISE_INSTALLS_DIR=<short root> (e.g. C:\mise) on Windows. Document this in mise's installation guide alongside the long-path-aware git config --system core.longpaths true.
  • Consider enabling Windows long-path support (Computer Configuration → Administrative Templates → System → Filesystem → Enable Win32 long paths), but note that not all third-party tools honor it.

11. Issue #9 — HKCU Path contains stale, version-pinned mise\installs entries

Evidence (April 10 audit)

At time of audit, [Environment]::GetEnvironmentVariable("PATH","User") contained:

…\AppData\Local\mise\installs\arduino\1.4.1
…\AppData\Local\mise\installs\deno\2.6.9
…\AppData\Local\mise\installs\node\25.6.1
…\AppData\Local\mise\installs\python\3.11.14
…\scoop\apps\mise\current\bin     ← dead, scoop no longer used

None of these were current mise versions. They appear to have been persisted by past coding-agent fixes that snapshotted the active mise activate output and wrote it to User PATH via setx. They shadow current versions and propagate into Git Bash ORIGINAL_PATH and WSL /mnt/c imports.

Effect

  • Wrong-version shadowing in cmd / pwsh -NoProfile (when activate hasn't run yet).
  • Dead Scoop entry remains because the Scoop uninstaller doesn't clean User PATH.
  • Indirectly inflates total PATH length, contributing to the >6000-char PATH the user observes.

Mitigation

Apply a one-shot cleanup script (back up first):

$backup = "C:\Users\$env:USERNAME\PATH_USER_BACKUP-$(Get-Date -f yyyyMMdd-HHmmss).txt"
[Environment]::GetEnvironmentVariable("PATH","User") | Out-File -Encoding utf8 $backup

$userPath = [Environment]::GetEnvironmentVariable("PATH","User")
$cleaned  = ($userPath -split ';') |
  Where-Object { $_ -and `
                 ($_ -notmatch '\\mise\\installs\\') -and `
                 ($_ -notmatch 'scoop\\apps\\mise\\') } |
  Select-Object -Unique
$newPath = $cleaned -join ';'

# Inspect before applying:
$newPath
# [Environment]::SetEnvironmentVariable("PATH",$newPath,"User")

The user has performed similar cleanups in the past (backups visible at C:\Users\Gabriel\PATH_*_BACKUP-2026041*.txt).


12. Issue #10 — mise shims are not in persistent Path (only injected by activate)

Symptom

mise doctor on this machine reports shims_on_path: yes only after mise activate runs. Before that:

  • cmd /c node works only because mise\installs\node\26.x happens to be in User PATH from the legacy entries — fragile.
  • pwsh -NoProfile / VS Code task runner / scheduled tasks all skip activate and lose mise tools.

Why this is a problem in practice

mise's default strategy on Windows is "rely on activate". activate only fires from interactive shells with a profile that calls it. Anything spawned without a profile loses mise's PATH prelude.

Recommendation

Put %LOCALAPPDATA%\mise\shims in User Path persistently. mise activate already prepends, so when both are present, activate's bin paths win and shims are the fallback for non-interactive shells.

$shims = "$env:LOCALAPPDATA\mise\shims"
$user  = [Environment]::GetEnvironmentVariable("PATH","User")
if ($user -notlike "*$shims*") {
  [Environment]::SetEnvironmentVariable("PATH", "$shims;$user", "User")
}

mise's installer for Windows could do this by default (it already creates the shims directory).


13. Issue #11 — WSL: appendWindowsPath + root + /mnt/c config bleed

Evidence

From the doctor report May 9:

  • WSL bash -lc 'command -v node' returns /root/.local/share/mise/installs/node/25.9.0/bin/node (Windows is on 26.0.0).
  • mise doctor in WSL reports config_files: /root/.config/mise/config.toml, /mnt/c/Users/Gabriel/.config/mise/config.toml — WSL is picking up the Windows config because CWD was /mnt/c/Users/Gabriel.
  • WSL PATH contains the entire Windows PATH after : separator (~100 entries under /mnt/c/...), creating spurious npm resolutions and other interop confusion.
  • WSL session runs as root (no [user] default = … in /etc/wsl.conf).

Effect

  • npm resolves to a Windows npm.cmd via interop, but node resolves to Linux mise's node — mismatched runtime → npm shell commands fail.
  • Stale aqua: tools / github: tools that the user has on Windows but never installed in WSL show as (missing).
  • Installing a tool in WSL writes to /root/.local/share/... which won't migrate cleanly if a user account is later added.

Mitigation — /etc/wsl.conf

[boot]
systemd = true

[interop]
appendWindowsPath = false

[user]
default = gabriel

Then create the user, chown -R gabriel:gabriel /root/.local/share/mise if you want to migrate (or reinstall under the new user).

The user's MISE_TRUSTED_CONFIG_PATHS does not include /mnt/c/... by default, but mise still discovers it via the parent-directory walk. To prevent that, either:

  • cd ~ at WSL start, or
  • Set MISE_CONFIG_FILE=/home/gabriel/.config/mise/config.toml in WSL profile to short-circuit discovery, or
  • Place an empty .mise.toml in /mnt/c to block walking further up.

14. Issue #12 — Prompt fragmentation (opinionated)

This isn't a mise issue; it's a user-experience issue that compounds the others.

Shell Current prompt Identity in prompt?
cmd default C:\> none
pwsh 7 oh-my-posh no shell tag
Windows PowerShell 5.1 default none
Git Bash bash default + _mise_hook none
WSL bash default none
WSL zsh starship + oh-my-zsh yes (via starship)

When the user switches between cmd → pwsh → git-bash → WSL trying to repro a mise bug, the prompt doesn't distinguish them — leading to "wait, am I in WSL or git-bash?" moments that prolong every diagnostic loop.

Opinionated recommendation (not required for mise to work)

  • Starship as the universal prompt. Already installed via mise (C:\mise\starship\<v>\). Single config in ~/.config/starship.toml. Supports cmd via Clink, pwsh, PowerShell 5, Bash, Zsh, Fish, etc.
  • Add an explicit [custom.shell_id] block (or use $env) so every shell shows pwsh7 / winps5 / cmd / git-bash / wsl-bash / wsl-zsh as a tag in the prompt.
  • Clink for cmd.exe. Without Clink, cmd cannot host a Starship prompt. Install via WinGet (chrisant996.Clink) or scoop; add Starship integration via clink set prompt.async true + Starship's clink loader.

Sample starship snippet:

# ~/.config/starship.toml
format = "$custom$username$hostname$directory$git_branch$git_status$character"

[custom.shell_id]
when = true
command = "echo $env:SHELL_ID"
style = "bold purple"
format = "[$output ]($style)"

Then in each shell's profile, set SHELL_ID early:

  • pwsh: $env:SHELL_ID = 'pwsh7'
  • Windows PS: $env:SHELL_ID = 'winps5'
  • cmd (via Clink Lua): os.setenv("SHELL_ID","cmd")
  • Git Bash: export SHELL_ID=git-bash
  • WSL bash: export SHELL_ID=wsl-bash
  • WSL zsh: export SHELL_ID=wsl-zsh

15. Recommended hardened setup for mise on Windows

Apply once on a new machine, in this order. Each step is independent and idempotent.

15.1 Persistent env vars (HKCU)

[Environment]::SetEnvironmentVariable("MISE_INSTALLS_DIR", "C:\mise", "User")
[Environment]::SetEnvironmentVariable("MISE_EXPERIMENTAL", "1", "User")  # optional

15.2 Add mise shims to persistent Path

$shims = "$env:LOCALAPPDATA\mise\shims"
$user  = [Environment]::GetEnvironmentVariable("PATH","User")
if ($user -notlike "*$shims*") {
  [Environment]::SetEnvironmentVariable("PATH","$shims;$user","User")
}

15.3 Clean stale mise\installs entries from User Path

See snippet in Issue #9. Backup first.

15.4 Reduce filesystem contention for npm-heavy installs

Blanket-excluding the mise install root from AV scanning is not recommended — that root holds third-party tarballs and binaries from many sources (npm, aqua, GitHub releases), and real-time scanning is the layer that catches a compromised release at unpack time. Instead, apply these proactive mitigations:

  1. Serialize mise install for npm-heavy operations:
    $env:MISE_JOBS = '1'    # session-scoped; remove when done
    mise install
    Or per-invocation: mise install --jobs 1.
  2. Enable Win32 long-path support at the OS level (one-time, requires admin + reboot):
    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
      -Name "LongPathsEnabled" -PropertyType DWORD -Value 1 -Force
  3. Keep %LOCALAPPDATA%\npm-cache and C:\mise on a fast local SSD (NVMe ideal). Slow / network-mounted disks widen the scan-vs-extract window.
  4. Have a clean-and-retry helper ready for when an mise install npm:* does fail partway. Drop this into your pwsh profile:
    function mise-reinstall-npm {
      param([string]$Pkg)
      $name = ($Pkg -replace 'npm:', '' -replace '@.*', '' -replace '/', '-' -replace '@', '')
      Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "C:\mise\npm-$name"
      mise install $Pkg --force
    }
    # usage: mise-reinstall-npm 'npm:gsd-pi@2.82.0'

If your security policy explicitly permits a scoped, time-bound AV exclusion on the install root, that does shrink the race further — but treat it as a deliberate trade-off, not as default setup, and re-evaluate periodically.

15.5 ~/.config/mise/config.toml settings block

[settings]
idiomatic_version_file_enable_tools = ["node", "pnpm"]

[settings.node]
corepack = false

[settings.npm]
package_manager = "npm"   # do NOT leave on Auto to avoid Issue #2

15.6 ~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1 (sketch)

# === mise activation ===
$env:MISE_EXPERIMENTAL = '1'
$env:SHELL_ID = 'pwsh7'
mise activate pwsh | Out-String | Invoke-Expression

# === secrets: load from .env file, NOT inline ===
$envFile = "$HOME\.config\dev.env"
if (Test-Path $envFile) {
  Get-Content $envFile | Where-Object { $_ -match '^\s*([^#=]+)\s*=\s*(.+)\s*$' } |
    ForEach-Object {
      $name, $value = $matches[1], $matches[2]
      [Environment]::SetEnvironmentVariable($name, $value, 'Process')
    }
}

# === starship prompt ===
Invoke-Expression (& starship init powershell)

Where ~\.config\dev.env contains:

XXX_KEY=<REDACTED>
yyy_TOKEN=<REDACTED>
zzz_API_KEY=<REDACTED>

…with icacls restricting it to the user.

15.7 Git Bash (~/.bashrc)

Reset to a minimal, dynamic activation:

# bash, for Git Bash on Windows
export PATH="$HOME/.local/bin:$PATH"
export SHELL_ID=git-bash
eval "$(mise activate bash)"

# starship
eval "$(starship init bash)"

# secrets
[ -f "$HOME/.config/dev.env" ] && set -a && source "$HOME/.config/dev.env" && set +a

Do not put a hardcoded export PATH='C:\...' string anywhere — it splits on : and produces ~150 garbage entries (the original .bashrc corruption documented in the April 10 audit).

15.8 cmd.exe via Clink

winget install chrisant996.Clink

Then in %LOCALAPPDATA%\clink\starship.lua:

load(io.popen('starship init cmd'):read("*a"))()
os.setenv('SHELL_ID', 'cmd')

15.9 WSL /etc/wsl.conf

[boot]
systemd = true
[interop]
appendWindowsPath = false
[user]
default = gabriel

Restart WSL (wsl --shutdown). Bootstrap mise inside the distro normally.

15.10 Verification — drop in verify-mise.ps1 and run from a fresh cmd

# Verify mise resolves under all six shell environments
function probe($name, $cmdline) {
  $out = (& cmd /c $cmdline 2>&1) -join "`n"
  "[$name] $out"
}

probe "cmd /c"           'node -v && npm -v && mise --version'
probe "pwsh -NoProfile"  'pwsh -NoProfile -c "node -v; npm -v; mise --version"'
probe "pwsh -Login"      'pwsh -Login -c "node -v; npm -v; mise --version"'
probe "git-bash -lc"     '"C:\Users\Gabriel\AppData\Local\Programs\Git\bin\bash.exe" -lc "node -v && npm -v && mise --version"'
probe "wsl bash -lc"     'wsl bash -lc "node -v && npm -v && mise --version"'

Each line should produce the same node/npm/mise versions. Any divergence flags one of the issues above.

15.11 Pre-flight checklist for UI tools (Claude Desktop, VS Code MCP, etc.)

Any tool that runs your mise-shimmed binaries from a scrubbed env needs:

"env": {
  "MISE_INSTALLS_DIR": "C:\\mise",
  "PATHEXT": ".COM;.EXE;.BAT;.CMD",
  "SystemRoot": "C:\\WINDOWS"
}

Some tools also need USERPROFILE and TEMP preserved. Until Issue #1 is fixed upstream, this trio is the minimum.


16. Investigation trail (artifacts left on disk)

For future archeologists / to back the claims in this document.

Probes and simulations (C:\tmp\)

  • repro-mise-issue.ps1 — the canonical minimal repro of Issue #1.
  • simulate-claude.ps1, simulate-claude2..11.ps1 — incremental tests pinning down which env vars matter. Each variant adds one var (USERPROFILE, LOCALAPPDATA, MISE_INSTALLS_DIR, PATHEXT, SystemRoot, COMSPEC, etc.). The sequence of results across these files is the empirical isolation of "needs PATHEXT and SystemRoot".
  • simulate-shim.ps1, simulate-shim2..4.ps1 — narrower tests of the shim itself.
  • test-final-config{,2}.ps1, test-direct-mise.ps1, test-shim-env.ps1, test-which.ps1 — terminal verifications.
  • test-claude-env.ps1 — original (timed-out) version that motivated the variants.
  • mise-env-probe/ — minimal fake npm package whose postinstall dumps process.env to postinstall-env.txt. Used to confirm that direct npm install --global --prefix propagates env correctly (it does); the failures in Issue #6 are NOT from missing env in the parent npm.

Logs (C:\tmp\)

  • postinstall-env.txt — captured env from the probe install (PATH=5741 chars, PATHEXT correct, SystemRoot correct, MISE_INSTALLS_DIR correct).
  • mise-install-verbose.log, mise-install-verbose-v2.log — mise install runs showing the aube auto-select bug and its workaround.
  • repro-v3.log, repro-v4.log — successful and failing mise install retries for gsd-pi.

Original npm failure trace (%LOCALAPPDATA%\npm-cache\_logs\)

  • 2026-05-12T11_26_51_673Z-debug-0.log — qwen-code (✅)
  • 2026-05-12T11_26_51_722Z-debug-0.log — cline (❌ sharp postinstall)
  • 2026-05-12T11_26_52_047Z-debug-0.log — gsd-pi (❌ sharp postinstall)
  • 2026-05-12T11_26_53_381Z-debug-0.log — vite-plus (✅)

Doctor scripts and reports (C:\Users\Gabriel\)

  • windows-shell-doctor.ps1 (49KB) — read-only PowerShell script that probes all 6 shells, PATH, registry, mise, prompt tools.
  • doctor-report.txt (Apr 12) — older snapshot.
  • windows-shell-report.txt (May 9) — latest snapshot. Contains the empirical numbers cited in this report.
  • mise-environment-audit.md (Apr 10, 109KB) — full forensic audit of the user's environment with 11+ findings; predecessor to this document.
  • PATH_*_BACKUP-*.txt — backups created during past User PATH cleanups.

Source code referenced (C:\Users\Gabriel\Projects\OSS\mise\)

  • src/env.rs:122-136 — env-var static definitions.
  • src/cli/exec.rs:387 — the which::which_in call that emits cannot find binary path.
  • src/shims.rswhich_shim, add_shim, remove_shim_with_rename_fallback.
  • src/backend/npm.rs:101, 609-622 — aube optional-dep + Auto-selection.
  • crates/mise-shim/src/main.rs — the 37-line shim.

Drafts ready for upstream

  • C:\tmp\mise-discussion-draft.md — Discussion draft for jdx/mise (Issue #1, with the .cmd/.bat caveat).
  • C:\tmp\mise-windows-findings.md — earlier consolidated findings (Issues #2-#7).
  • C:\tmp\mise-issue-draft.md — older alternate draft, superseded by the discussion.

17. How to engage upstream

jdx/mise has blank_issues_enabled: false in .github/ISSUE_TEMPLATE/config.yml. The contribution guide (https://mise.en.dev/contributing.html) explicitly directs bug reports and feature requests to Discussions first; PRs require a discussion to align.

Suggested sequence:

  1. Open the Discussion from C:\tmp\mise-discussion-draft.md (Issue #1: clean-env shim). Highest reproducibility, clearest minimal repro, lowest risk of being closed as user error. If well-received and a maintainer asks for an issue, it converts cleanly.
  2. After response on #1, separate Discussion for Issue #2 (aube auto-select). Even shorter: a single-paragraph repro and a 1-line fix proposal at src/backend/npm.rs:609.
  3. Issue #3 + #4 (stale state on failure / no installs_dir setting) can be combined into a "Windows install-time hygiene" discussion since they share a theme.
  4. Issue #5 (mise-shim no fallback) probably belongs as a follow-up after #1 — the maintainer may decide to fix all four under a single Windows-hardening PR.

Do not bundle Issues #6-#7 (sharp / npm extract races) into a mise discussion. Those are npm/Defender interactions; mise can only document.


18. What I'd change next on this machine

In priority order, if I were the user:

  1. First pass, low risk: apply persistent-env (15.1) + add-shims-to-PATH (15.2) + the npm.package_manager = "npm" setting (15.5) + the contention mitigations (15.4, especially MISE_JOBS=1 and long-path support). Zero risk of regression and removes the most common failure surfaces.
  2. This week: clean the stale mise\installs\… entries from User Path (15.3) — back up first.
  3. This week: post the Issue #1 Discussion to jdx/mise.
  4. Later: WSL hardening (15.9) — flagged "moderate risk" because changing default user requires careful migration of mise installs under /root/.
  5. Opinionated, low priority: Clink + unified Starship across all 6 shells (Issue #12). Strictly observability; doesn't fix any functional problem.

Appendix A. Sanitization note

API keys and bot tokens were observed in:

  • C:\Users\Gabriel\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
  • C:\Users\Gabriel\.config\mise\config.toml ([env] block)
  • C:\Users\Gabriel\.zshenv (WSL)
  • Various mise-environment-audit.md references

Specifically: XXX_KEY, YYY_TOKEN (active + commented older bot), ZZZ_KEY, and WWW_TOKEN (the last is dynamically fetched via WWW token, safer than the others).

Recommendation regardless of the rest of this report:

  • Rotate XXX_KEY, YYY_TOKEN, ZZZ_KEY now.
  • Move all of them out of profile files into ~/.config/dev.env with restricted ACLs (Windows: icacls $HOME\.config\dev.env /inheritance:r /grant:r "$env:USERNAME:F"; WSL: chmod 600).
  • Use [env] in mise config.toml only for non-secret path-like vars (e.g. MINI_ALICE_PATH).

Tokens that have appeared in shell sessions, agent contexts, or .txt reports should be considered compromised even if no leak has been observed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment