bootstrap.user.login_shell now accepts a shell name such as fish instead of a full path. [doctor.checks] can use templates, and vfox backend plugins get their own cache directory. This release also fixes PATH handling in zsh setups that deduplicate PATH, stale task cache hits, a pipx interpreter mix-up on macOS, and removes trust in the compromised eMudhra CA certificates.
It also includes the changes prepared for 2026.10.16, which was tagged but never published: relative and templated write_targets paths, version aliases from vfox backend plugins, keyring passwords for private PyPI indexes, vfox-backend: packslip plugin sources, and a change to where find_up starts (see Changed and Upgrading).
Added
-
[bootstrap.user].login_shellaccepts a shell name. A config shared across machines no longer needs to know where each system installs the shell. mise looks up the name when it checks or applies the setting, which happens afterbootstrap.packageshas installed the shell. It searchesPATHfirst (never the mise shims directory), then common package-manager locations:/opt/homebrew/bin,/usr/local/bin,/home/linuxbrew/.linuxbrew/bin,~/.linuxbrew/bin,/usr/binand/bin. If no executable is found,mise bootstrap user applyfails without changing/etc/shellsor the account, andstatusreportsnot installed.mise bootstrap user statusshows the path it found, and--jsonoutput has a newresolvedfield. Absolute paths work as before. #14770 (@jdx)[bootstrap.packages] "brew:fish" = "latest" [bootstrap.user] login_shell = "fish"
-
Doctor checks support templates.
run,descriptionandhintin[doctor.checks.*]are rendered with the template context of the config file that declares them, the same way hooks are. A check can now use a value from[vars]instead of hard-coding it.diris not rendered. If a template fails, only that check reportsERRORand the other checks still run. Text that already contains{{or{%in these fields is now parsed as a template. #14771 (@jdx)[doctor.checks."project-a:env"] description = "project-a has a .env file" run = 'test -f {{ vars.repos_dir | quote }}/project-a/.env' hint = "cp {{ vars.repos_dir }}/project-a/.env.example {{ vars.repos_dir }}/project-a/.env"
-
vfox plugins get
RUNTIME.cacheDirPath. This is a cache directory for each plugin at$MISE_CACHE_DIR/plugins/<plugin-name>. mise creates it before running any hook, and every hook of the plugin sees the same path. UnlikepluginDirPath, it is kept when the plugin is reinstalled.mise cache cleardeletes it, so plugins must handle it being missing. It isnilwhen no cache directory is available. #14776 (@jdx) -
settings.write_targetsaccepts relative and templated paths. Before, only absolute or~/paths worked, so dotfiles shared between machines with differentMISE_CONFIG_DIRorXDG_CONFIG_HOMEhad to hardcode the config directory. Templates are now rendered, and a relative result is joined to theconfig_rootof the settings file that declares it. For a global file that is$HOME, so the portable spelling uses the file's own directory. The resolved path must still be a global config file that mise loads. If a template fails to render, mise warns, andmise use --globalfails with "must resolve to an absolute path" instead of writing to the default file. #14742 (@jdx)# ~/.config/mise/conf.d/settings.toml [settings.write_targets] tools = "{{ config_source | dirname }}/tools.toml"
-
vfox backend plugins can report version aliases.
BackendListVersionscan return analiasestable, so a registry's moving names resolve to real versions. Before,"mise-ghcup:ghc" = "recommended"passed the literal string to the plugin, installed intoinstalls/.../recommended, never followed the tag when it moved, and showedrecommendedinmise lsand the lockfile. Nowghc@recommendedresolves to9.10.3, installs into9.10.3/, and the lockfile records9.10.3. Aliases you define in config still win. They are cached like the version list, and an expired cache still answers offline. Older mise versions ignore thealiasesfield, so plugins can adopt it right away. #14732 (@jdx, @cprecioso)return { versions = { "9.8.4", "9.10.3", "9.14.1" }, aliases = { recommended = "9.10.3" }, }
-
mise lockand version listing work with a PyPI index that uses a keyring. With an index such as Azure Artifacts andartifacts-keyring,mise installworked because uv read the password from its keyring provider, but mise's own requests sent no credentials and failed with401 Unauthorized. Set the newpypi.keyring_provider(disabledby default, orsubprocess; env varMISE_PYPI_KEYRING_PROVIDER) and give the registry URL a username without a password. mise then runskeyring get <url> <username>, as uv does, and sends the result as HTTP Basic credentials when it lists versions and whenmise lockreads a release'srequires-python. The password is never written to the lockfile or the version cache. Thekeyringexecutable must be onPATH. If it is missing or has no entry, mise sends the request without credentials. #14733 (@jdx)[settings.pypi] keyring_provider = "subprocess" registry_url = "https://VssSessionToken@pkgs.dev.azure.com/org/project/_packaging/feed/pypi/simple/{}/"
-
vfox-backend:works as a prefix for packslip plugin sources. A vfox backend plugin can be installed from a signed packslip archive, both from the CLI and in[plugins]. Before,mise plugins install vfox-backend:foo 'packslip:owner/repo#1.0.0'failed withpackslip plugin sources require the vfox plugin type. An explicitasdf:orpackage:prefix is still rejected, and the error now says "vfox or vfox-backend". The archive still declaresextensions.mise.plugin = "vfox". #14727 (@jdx)mise plugins install vfox-backend:my-backend 'packslip:your-org/my-backend#1.0.0' -
mise bootstrap packages listis now an alias forstatus, alongsidels. #14709 (@himkt)
Changed
-
find_upinenv._.filestarts at the config root. Every other relative path in a config resolves against the config root, butfind_upstarted at the current directory. A relativefind_uppath now starts at the config root and walks up through its parents, which suits a monorepo whose root.envapplies to every package. An absolute path starts at its own directory. A global config that wants to search from wherever mise runs can usecwd. Theenv_filesetting still discovers dotenv files up from the current directory and is unchanged. See Upgrading. #14734 (@jdx, @thernstig)# ~/.config/mise/config.toml [env] _.file = { path = "{{cwd}}/.env", find_up = true }
-
allow_buildsapproves only the packages it lists. With the embedded aube installer,allow_builds = ["re2"]also ran the install scripts of every package on aube's bundled trusted-dependencies list, such asdtrace-provider. For a package list, mise now turns that default list off, so only the packages you name build.allow_builds = trueand the default with no option are unchanged. This updates the embedded aube to 2.8.0. #14731 (@jdx)
Fixed
exec()in[env]values, and other commands mise runs with the pre-activation environment, can now find commands in a_.pathdirectory that was already onPATHbefore activation, even if the shell deduplicatesPATH(for example zsh withtypeset -U path PATH). #14772 (@jdx)- With
task.source_freshness_hash_contents = true, a task now re-runs when a source file is changed to content of the same size and its old mtime is restored (for example byrsync -a,cp -portouch -r). On Unix, mise now also compares the file's ctime. Cache entries written by older versions are rehashed once. #14778 (@jdx) - On macOS,
pipx:installs no longer fail whenaqua:aws/aws-cliis in the toolset. Because macOS filesystems ignore case by default, mise's lookup forpythonmatched aws-cli's executablePythonshared library and passed it to uv as the interpreter. This lookup now requires the exact case. #14769 (@jdx) - An aqua registry template with an unterminated string literal now fails with an "unterminated string literal" error instead of hanging. Escaped quotes after backslashes are also detected correctly. #14752 (@Huge, @jdx)
- The built-in npm backend no longer trusts the eight eMudhra intermediate CA certificates whose private keys were compromised. The fix comes from updating its bundled root certificates (
webpki-root-certs1.0.10,rustls-webpki0.103.16). Builds that use the OS trust store are fixed by OS updates instead. #14781 (@jdx) - A
[bootstrap.compose]project no longer reports drift on every run when a service uses aservice:reference such asnetwork_mode: service:vpn. Compose hashes the reference as written but stamps the container with the hash of the resolvedcontainer:<id>, so the two never matched. mise now pins these references (network_mode,ipc,pid,volumes_from) to the running container before it asks compose for the hash. This needs an explicitfileslist in the project's config. Projects without one hash as before. #14729 (@jdx) - vfox plugins now keep symlinks when they extract
.ziparchives returned byPreInstall. Before, entries such asbin/npm -> ../lib/node_modules/npm/bin/npm-cli.jsbecame regular files holding the link target, and the tool failed withNo such file or directory. Links that resolve outside the install directory are still rejected. Thehttp:backend and tarballs were not affected. #14730 (@jdx) [daemons.<name>] task = "worker"now finds a file task such as.mise/tasks/worker.sh, asmise run workerdoes. Before, it failed withdaemon srv runs unknown task "worker"unless you wroteworker.sh. #14741 (@jdx)- On Windows,
mise self-updateno longer replaces the wingetmise-shim.exelink with a copy. It updates the realmise-shim.exenext to the realmise.exe. Before,mise reshimkept warning that the shim was older than mise. #14737 (@jdx) - An inherited
[dotfiles]entry turned off with onlyenabled = falseno longer printsno recognized operation (block, source, or line), ignoring entryon every command. Other tables with no recognized operation, such as a misspelledline, still warn. #14726 (@jdx, @ponko2)
Upgrading
find_upin a global config: a relativefind_upentry inenv._.fileused to search from the current directory. It now searches from the config root, which is~for a global config, so it no longer finds project files. To keep the old behavior, put{{cwd}}/in front of the path.find_upwas added in 2026.10.12.
Documentation
- The dotfiles docs now explain that a group entry with only
permissionsdoes not inherit the group'smode, so it deploys nothing. Removingmodefrom an entry that used to deploy a file leaves that file orphaned, and--prunedeletes it. To deploy a file and set its permissions, use{ mode = "copy", permissions = "0600" }. #14768 (@jdx)
New Contributors
Full Changelog: v2026.10.15...v2026.10.17
💚 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.