jdx/mise · error

recursive shim invocation detected for {bin_name}: {}

Error message

recursive shim invocation detected for {bin_name}: {}

What it means

Windows-only recursion guard in shim execution: mise stores the invoking shim's path in the MISE_SHIM_PATH environment variable; when a shim runs, it canonicalizes that previous value and compares it to the current shim path. If they are equal, the shim is about to execute itself and mise bails immediately instead of recursing until the process stack or PATH resolution explodes. Non-Windows platforms skip this check entirely.

Source

Thrown at src/shims.rs:44

pub async fn handle_shim() -> Result<()> {
    // TODO: instead, check if bin is in shims dir
    let bin_name = *env::MISE_BIN_NAME;
    if env::is_mise_binary(bin_name) || cfg!(test) {
        return Ok(());
    }
    #[cfg(windows)]
    {
        let shim_path = invoked_shim_path();
        if env::var_path(env::MISE_SHIM_PATH_ENV)
            .as_ref()
            .is_some_and(|previous| {
                file::paths_eq(
                    &file::canonicalize_or_self(previous),
                    &file::canonicalize_or_self(&shim_path),
                )
            })
        {
            bail!(
                "recursive shim invocation detected for {bin_name}: {}",
                display_path(&shim_path)
            );
        }
        *env::MISE_SHIM_PATH.write().unwrap() = Some(shim_path.clone());
        env::set_var(env::MISE_SHIM_PATH_ENV, &shim_path);
    }
    let mut config = Config::get().await?;
    let mut args = env::ARGS.read().unwrap().clone();
    env::PREFER_OFFLINE.store(true, Ordering::Relaxed);
    trace!("shim[{bin_name}] args: {}", args.join(" "));
    let (bin, ts) = which_shim(&mut config, &env::MISE_BIN_NAME, &args).await?;
    args[0] = bin.to_string_lossy().to_string();
    env::set_var("__MISE_SHIM", "1");
    let exec = Exec {
        tool: vec![],
        c: None,
        command: Some(args),

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Run `mise which <bin>` to find the real executable and make wrapper/child scripts call that absolute path instead of the bare command name.
  2. Reinstall the tool (`mise install <tool>@<version>`) if the real binary was removed, then `mise reshim`.
  3. Fix PATH ordering inside scripts: the activated tool's install dir must come before (or instead of) the shims dir when re-invoking tools.
  4. Rename any custom wrapper so it does not collide with the shim/bin name it delegates to.

Example fix

# before: wrapper re-invokes 'node', which resolves to the same shim
node server.js
# after: resolve the real binary first
"$(mise which node)" server.js
Defensive patterns

Strategy: validation

Validate before calling

# Windows wrapper scripts: resolve the real binary instead of the bare name
bin=$(mise which node) || { echo 'node not installed' >&2; exit 1; }
"$bin" server.js

Try / catch

Catch the 'recursive shim invocation detected' message from mise on Windows; report the offending shim path and instruct fixing PATH ordering or reinstalling — do not re-run the same command.

Prevention

When it happens

Trigger: On Windows, a shim's target re-executes the same command name in a way that resolves back to the same shim file — e.g. a tool wrapper script calling `node` when the shims directory is the only source of `node` on PATH, or a shim/launcher named identically to the bin it wraps. Detected via env::MISE_SHIM_PATH matching invoked_shim_path().

Common situations: Custom wrapper scripts in the shims dir or earlier on PATH that re-exec the tool by name; tools that spawn themselves (language servers, watchers) while the real toolchain directory is not on PATH inside the child; broken installs where the real executable was deleted so the shim keeps resolving to itself; scripts that prepend the shims dir over the activated tool bin dir.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/7ca8698865ff1e33. Report an issue: GitHub.