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
miseon 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.
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:
- 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, andmisehas nothing to fall back to. MISE_INSTALLS_DIRcannot be persisted outside an env var. If the user moves installs toC:\miseto avoidMAX_PATH, that knowledge is lost the moment env propagation is broken.mise-shim.exeis a 37-line wrapper that only resolvesmisethroughPATH, with no fallback whenPATHdoesn't have it.- mise auto-prefers
aubewhen it seesaubeinstalled anywhere on disk, even ifaubeisn't a member of the active toolset — instantly breaking everymise install npm:*until the user finds the setting to override it. - mise's failure-cleanup is brittle on Windows — when an
npminstall fails, mise tries torm -rfthe 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.
| 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.
| # | 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 | npm/Windows/AV interaction | |
| 7 | npm tar extraction races: Cannot cd into … warnings under mise install |
npm + Windows AV | Medium | 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.
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 npx→npx.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 |
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.
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-HostSource inspection of the local jdx/mise checkout (C:\Users\Gabriel\Projects\OSS\mise):
-
MISE_INSTALLS_DIRis env-only.src/env.rslines 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.tomlreader. Same pattern forMISE_DATA_DIR,MISE_SHIMS_DIR. If the env var is dropped by a UI tool, mise falls back to%LOCALAPPDATA%\mise\installsand finds nothing. -
mise xresolves.cmdvia the Rustwhichcrate.src/cli/exec.rs:387:let program = which::which_in(program, lookup_path, cwd)?;
On Windows,
which::which_inenumeratesPATHEXTextensions. With noPATHEXT, the.cmdextension is invisible, sonpx→C:\mise\node\26.x\npx.cmdis unresolvable. Error:cannot find binary path. -
Node.js needs
SystemRootforBCryptOpenAlgorithmProvider/ CSPRNG seeding. Without it, the process panics on startup before user code runs.
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.
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"
}
}
}
}- Persist
installs_dir(anddata_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). - Inject Windows essentials in
mise x's child env when missing. The shim is already wrapping a subprocess; before spawning, ifPATHEXT/SystemRoot/COMSPECare missing, populate them from the actually-installed Windows defaults. - Document the requirement. Add a "Running mise shims from non-shell processes (UI tools, CI)" section to
docs/dev-tools/shims.mdlisting the three env vars.
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.
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.
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 Auto → Aube, 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.
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 failsEither:
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 aubeif not wanted.
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.
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.
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.
Explicit cleanup before retry:
Remove-Item -Recurse -Force C:\mise\npm-<tool>\<ver>
mise install 'npm:<tool>@<ver>' --forceHelper 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
}- Retry the
rm -rfwith rename-out-of-the-way (similar to whatshims.rs::remove_shim_with_rename_fallbackalready does for locked.exeshims). - Promote the WARN to an ERROR when cleanup fails (so the user knows the next retry is unsafe).
- Refuse to start
--forceinstall on top of a partial tree without confirming cleanup succeeded.
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/.
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.
A persistable installs_dir setting would mean:
- Adding
MISE_INSTALLS_DIRto 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.
Add [settings] installs_dir = "C:\\mise" (and data_dir, shims_dir, cache_dir) support. Env var continues to override; file is the persistent default.
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); }
}
}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".
Before falling back to PATH:
- Look next to the shim itself (
<shim_dir>\..\..\..\..\bin\mise.exeor any nearby path). - Read a marker file written by
mise reshimthat records the absolute path of themise.exethat generated the shims. - Optionally also re-emit
MISE_INSTALLS_DIR/MISE_DATA_DIRfrom that marker, so the eventualmise xdoesn't depend on env propagation.
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.
Possible contributors, none definitively isolated:
- PATH propagation race: 4 npm processes each spawning
cmd.exesimultaneously; 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.
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).
- 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 throughsharp'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. Themise-reinstall-npmhelper in §15.4 automates this. - Pin transitive
sharpto a known-good version when possible, or report upstream togsd-pi/clinemaintainers so they prefer node-prebuild downloads over a local build. - AV exclusion of
C:\miseis 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.
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.
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).
- Serialize:
MISE_JOBS=1(ormise install --jobs 1) for npm-heavy tools. Removes most of the race. - Enable Windows long-path support at the OS level so npm/Node can use
\\?\-prefixed paths and avoid theMAX_PATHcliffs that compound with the AV race (GPO: Computer Configuration → Administrative Templates → System → Filesystem → Enable Win32 long paths; or registryHKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1; reboot). - Use a fast local SSD (ideally NVMe) for
%LOCALAPPDATA%\npm-cacheandC:\mise. Slow disks dramatically widen the scan-vs-extract window. - 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-npmhelper 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.
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.
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.
- Use
MISE_INSTALLS_DIR=<short root>(e.g.C:\mise) on Windows. Document this in mise's installation guide alongside the long-path-awaregit 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.
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.
- Wrong-version shadowing in cmd / pwsh
-NoProfile(whenactivatehasn'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.
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).
mise doctor on this machine reports shims_on_path: yes only after mise activate runs. Before that:
cmd /c nodeworks only becausemise\installs\node\26.xhappens to be in User PATH from the legacy entries — fragile.- pwsh
-NoProfile/ VS Code task runner / scheduled tasks all skipactivateand lose mise tools.
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.
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).
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 doctorin WSL reportsconfig_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 spuriousnpmresolutions and other interop confusion. - WSL session runs as
root(no[user] default = …in/etc/wsl.conf).
npmresolves to a Windowsnpm.cmdvia interop, butnoderesolves to Linux mise's node — mismatched runtime →npmshell 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.
[boot]
systemd = true
[interop]
appendWindowsPath = false
[user]
default = gabrielThen 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.tomlin WSL profile to short-circuit discovery, or - Place an empty
.mise.tomlin/mnt/cto block walking further up.
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.
- 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 showspwsh7/winps5/cmd/git-bash/wsl-bash/wsl-zshas a tag in the prompt. - Clink for cmd.exe. Without Clink,
cmdcannot host a Starship prompt. Install via WinGet (chrisant996.Clink) or scoop; add Starship integration viaclink 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
Apply once on a new machine, in this order. Each step is independent and idempotent.
[Environment]::SetEnvironmentVariable("MISE_INSTALLS_DIR", "C:\mise", "User")
[Environment]::SetEnvironmentVariable("MISE_EXPERIMENTAL", "1", "User") # optional$shims = "$env:LOCALAPPDATA\mise\shims"
$user = [Environment]::GetEnvironmentVariable("PATH","User")
if ($user -notlike "*$shims*") {
[Environment]::SetEnvironmentVariable("PATH","$shims;$user","User")
}See snippet in Issue #9. Backup first.
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:
- Serialize
mise installfor npm-heavy operations:Or per-invocation:$env:MISE_JOBS = '1' # session-scoped; remove when done mise install
mise install --jobs 1. - 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
- Keep
%LOCALAPPDATA%\npm-cacheandC:\miseon a fast local SSD (NVMe ideal). Slow / network-mounted disks widen the scan-vs-extract window. - Have a clean-and-retry helper ready for when an
mise install npm:*does fail partway. Drop this into yourpwshprofile: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.
[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# === 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.
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 +aDo 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).
winget install chrisant996.ClinkThen in %LOCALAPPDATA%\clink\starship.lua:
load(io.popen('starship init cmd'):read("*a"))()
os.setenv('SHELL_ID', 'cmd')[boot]
systemd = true
[interop]
appendWindowsPath = false
[user]
default = gabrielRestart WSL (wsl --shutdown). Bootstrap mise inside the distro normally.
# 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.
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.
For future archeologists / to back the claims in this document.
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 "needsPATHEXTandSystemRoot".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 whosepostinstalldumpsprocess.envtopostinstall-env.txt. Used to confirm that directnpm install --global --prefixpropagates env correctly (it does); the failures in Issue #6 are NOT from missing env in the parent npm.
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.
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 (✅)
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.
src/env.rs:122-136— env-var static definitions.src/cli/exec.rs:387— thewhich::which_incall that emitscannot find binary path.src/shims.rs—which_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.
C:\tmp\mise-discussion-draft.md— Discussion draft forjdx/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.
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:
- 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. - 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. - Issue #3 + #4 (stale state on failure / no
installs_dirsetting) can be combined into a "Windows install-time hygiene" discussion since they share a theme. - 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.
In priority order, if I were the user:
- 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, especiallyMISE_JOBS=1and long-path support). Zero risk of regression and removes the most common failure surfaces. - This week: clean the stale
mise\installs\…entries from UserPath(15.3) — back up first. - This week: post the Issue #1 Discussion to
jdx/mise. - Later: WSL hardening (15.9) — flagged "moderate risk" because changing default user requires careful migration of mise installs under
/root/. - Opinionated, low priority: Clink + unified Starship across all 6 shells (Issue #12). Strictly observability; doesn't fix any functional problem.
API keys and bot tokens were observed in:
C:\Users\Gabriel\Documents\PowerShell\Microsoft.PowerShell_profile.ps1C:\Users\Gabriel\.config\mise\config.toml([env]block)C:\Users\Gabriel\.zshenv(WSL)- Various
mise-environment-audit.mdreferences
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_KEYnow. - Move all of them out of profile files into
~/.config/dev.envwith restricted ACLs (Windows:icacls $HOME\.config\dev.env /inheritance:r /grant:r "$env:USERNAME:F"; WSL:chmod 600). - Use
[env]inmise config.tomlonly 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.