github jdx/mise v2026.9.10
v2026.9.10: shims.exclude, npm allow_exotic_deps, bulk dotfiles conflict resolution, and Windows Ctrl-C handling for tasks

5 hours ago

New settings let mise manage a tool without claiming its command names (shims.exclude) and approve non-registry npm dependencies (allow_exotic_deps), mise dot pull can decide every sharing conflict at once, and vfox plugin hooks gain cmd.stream plus a working cmd.exec timeout. On the fix side, mise run now survives Ctrl-C on Windows instead of orphaning cmd.exe, fish shells launched through shims start much faster with correct PATH order, and several task-resolution, lockfile, and Homebrew cask bugs are corrected.

Highlights

  • Control over what mise puts on PATH and what it installs: settings.shims.exclude keeps names like python resolving to the OS while mise still manages the tool; allow_exotic_deps approves specific npm packages fetched from git or tarball URLs; exact packslip: pins install during a minimum_release_age cooling window; and mise upgrade --bump keeps SemVer build metadata such as +k3s1.
  • Dotfiles sharing on a second machine: mise dot pull --take-remote-all / --keep-local-all resolve all conflicts in one command, paths that cannot be decided are held rather than aborting the pass, and a directory sitting where a tracked file belongs is now reported as exactly that.
  • Task and shell reliability: Windows Ctrl-C shuts tasks down cleanly, a task's own name always beats another task's alias, glob expansions no longer drop file tasks, Bash completion of ns:task names no longer duplicates the prefix, and fish startup through mise exec/shims is no longer quadratic in the number of tools.

Added

  • shims: settings.shims.exclude (env MISE_SHIMS_EXCLUDE) lists command names mise never creates shims for. The tool stays installed and version-qualified names like python3.12 still resolve through mise, but the excluded name resolves to whatever else is on PATH; existing shims for those names are removed on the next mise reshim. Note that under mise activate without --shims the tool's bin directory still joins PATH, and excluding python3 means python3 -m venv silently uses the system interpreter. (#13266)

    [settings.shims]
    exclude = ["python", "python3", "pip", "pip3"]
  • npm: allow_exotic_deps approves dependencies that aube's blockExoticSubdeps gate would otherwise block because they come from a git, file:, or direct tarball URL. List package names to exempt only those (the gate stays on for the rest of the graph), or set true to exempt the whole graph. Applies to the aube and aube_cli installers. Embedded-aube installs now also warn when install_env is set, since it never reached the in-process installer. (#13231)

    [tools]
    "npm:@gmickel/gno" = { version = "2.3.0", allow_exotic_deps = ["xlsx"] }
  • dotfiles: mise dot pull --take-remote-all and --keep-local-all decide every pending conflict at once, with per-path --take-remote/--keep-local naming exceptions. The two blanket flags are mutually exclusive, and paused-sync and adoption messages now point at them. (#13233)

    mise dot pull --take-remote-all --keep-local ~/.bashrc
  • bootstrap: Every string value in [bootstrap.linux.systemd.units] and [bootstrap.macos.launchd.agents] is rendered as a template before the unit file or plist is written, so {{ config_root }}/.env in environment_file resolves to the declaring config's directory. Values without template syntax (including %h and $HOME) pass through untouched, exec() is rejected, and a unit whose template fails is skipped by name without blocking the others. [bootstrap.services] is not yet templated. (#13227)

  • hooks: Each MISE_INSTALLED_TOOLS entry passed to postinstall hooks now carries requested_version (for example latest, 22, or an alias) alongside the resolved version, so a hook can tell a floating request from a pin. The field is always present; existing hooks reading name/version are unaffected. (#13274)

  • vfox plugins: cmd.stream runs a command with stdin connected and stdout/stderr streamed to the terminal, for hooks that genuinely need input such as a login or license prompt; it pauses the progress renderer and holds the terminal exclusively while it runs. cmd.exec and os.execute now detach stdin unless --raw is set, matching every other subprocess mise spawns, so a plugin that read stdin through os.execute should switch to cmd.stream. (#13261)

  • vfox plugins: The timeout option on cmd.exec (and cmd.stream) now works instead of being silently ignored. It takes seconds (fractions allowed); on expiry the spawned shell is killed and the call raises a catchable error. Only the shell mise spawned is killed, so background processes it started may keep running. (#13263)

    local ok, err = pcall(cmd.exec, "some-tool sync", { timeout = 30 })

Fixed

  • task: On Windows, pressing Ctrl-C during mise run no longer kills mise immediately and leaves a cmd.exe behind stuck on Terminate batch job (Y/N)?. The first Ctrl-C lets running commands exit and stops scheduling new tasks; a second one takes the remaining process tree down. Tasks ended by the console are reported as interrupted instead of failing with exit code -1073741510. (#13226)
  • task: A task's own name now always wins over another task's alias. Previously a parent config's tests task with alias = "test" could shadow a test task in the current directory, depending on alphabetical order. Aliases still resolve wherever no task claims that name. (#13230)
  • task: Glob expansions such as mise run '//...:lint' or '*:lint' no longer silently drop file tasks (mise-tasks/lint.sh) when a sibling package has an exact match. The same-package dedup that stops hello and hello.sh running twice is preserved. (#13277)
  • completions: Bash completion of namespaced tasks like update:deps:no<TAB> no longer produces update:deps:update:deps:no-cooldown. Reinstall the script with mise completion bash --install if yours predates the prefix-aware wrapper. (#13276)
  • exec: Launching fish through mise exec or a shim emitted one fish_add_path per directory, which made startup quadratic (over 1s with ~80 tools) and reversed mise's PATH order relative to bash. A single batched call restores both. (#13235)
  • dotfiles: A blanket --take-remote-all/--keep-local-all no longer aborts the whole pass when one path cannot be decided (a directory on the live side, or unsaved local changes under --keep-local-all). Decisions for the other conflicts are recorded, and the error names the held paths so fixing just those finishes the setup. (#13239, #13242)
  • dotfiles: A directory or unreadable path where the repository has a file is now reported by mise dot conflicts, mise dot status, and mise doctor as exactly that, with advice to move it aside, instead of as a "changed type" conflict that --take-remote/--keep-local cannot resolve. Git or process failures while reading a live file now stop the sync with their own error instead of posing as a conflict. (#13249)
  • upgrade: mise upgrade --bump preserves SemVer build metadata when rewriting a pin, so k3s bumps to 1.37.0+k3s1 rather than a nonexistent 1.37.0, and Temurin keeps its +7 build number. Coarser pins like 1.36 still bump to 1.37. (#13258)
  • packslip: An exactly pinned version (for example "packslip:github.com/jdx/hk" = "2.0.1") now installs and locks while still inside its minimum_release_age window, as the setting documents. Fuzzy requests such as "2" or latest still wait out the cutoff. (#13251)
  • install: MISE_LOCKED=1 mise install <tool> no longer warns about unrelated (often global) tools missing from the lockfile; installing the requested tool or a bare mise install still fails if that tool is not locked. (#13259 by @jamescassell)
  • pypi: mise lock no longer fails when a with/expose requirement is pinned to a release needing a newer Python than the tool itself (e.g. mkdocs 1.6.1 with mkdocstrings==1.0.6). The sidecar's requires-python is now intersected across every pinned requirement; unpinned requirements and pins behind an interpreter marker leave the range alone. Existing lockfiles remain valid. (#13252)
  • aqua: With minimum_release_age set, the latest release no longer falls back to an older version when the hosted version list lags GitHub. The release date from the /releases/latest response mise already fetched is used directly, with no extra requests. (#13228)
  • backend: Tools whose registry entry splits across backends at a version boundary (like hk) now list versions from the backend that actually resolves, so mise ls-remote hk@1.57 and mise latest hk@1.57 return 1.57.0 instead of nothing. Also covers backends promoted by MISE_DISABLE_BACKENDS, platform-scoped entries, and lockfile pins. (#13238)
  • http: GitHub answers an exhausted rate limit with 403 rather than 429, so mise never retried it. A 403 carrying x-ratelimit-remaining: 0 or retry-after is now retried like a 429 under http_retries; a 403 with quota remaining is still treated as a refusal. Default backoff (~5s total) will not outlast a long reset, but brief contention no longer fails an install outright. (#13256)
  • skills: mise skills ls and mise skills sync now warn when a packslip declares a skill the install does not hold, with the reason (skills.fetch off, packslip.exec off, or a failed download), instead of looking identical to "no skills declared". After an install with skills.auto_sync off, a one-time hint points at mise skills sync. --json output is unchanged. (#13275)
  • brew: adopt is now honored for casks named on the command line (mise bootstrap packages apply brew-cask:menuwhere) and for tap-qualified names and aliases like brew-cask:homebrew/cask/firefox, so existing app bundles are adopted rather than replaced and macOS keeps their Privacy & Security grants. (#13262)
  • brew: Tap formulae declaring requirement symbols such as depends_on :macos no longer make bootstrap packages try to fetch a formula named macos and abort the whole run with a 404. (#13240 by @waynehoover)
  • brew: Tap cask metadata evaluation now understands appdir and HOMEBREW_PREFIX interpolation, and casks whose app bundle sits in a nested archive directory (app "nested/Example.app") install as Example.app instead of being rejected as a relative target; duplicate app targets are rejected before anything is downloaded. (#13138 by @Guria, #13199 and #13200 by @soodoh)
  • bootstrap: Selecting a Ruby to evaluate third-party Homebrew taps skips mise shims, which the metadata sandbox could not load, so package bootstrap no longer fails when Ruby is installed through mise. (#13198 by @jacobbednarz)

Documentation

  • The dotfiles history guide now explains encryption recipients (SSH keys, age-keygen, recovery keys) and warns that passphrase-protected SSH keys and plugin-only recipients cannot decrypt in the background; the setup guide covers adopting onto a machine that already has the files and using non-GitHub Git hosts. (#13232)
  • The PyPI backend's locking limitations now point at the lockable with, expose, and dependency_prereleases options. (#13222)

New Contributors

Full Changelog: v2026.9.9...v2026.9.10

💚 Sponsor mise

mise is built and maintained by @jdx, an open source developer at entire.io, the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an individual or company sponsor. Your support funds ongoing development and helps keep mise fast, free, and independent.

Don't miss a new mise release

NewReleases is sending notifications on new releases.