v2026.10.17

jdx/misev2026.10.17Oct 21, 2026by mise-en-dev

Upgrading?

Full Release Notes

`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_shell` accepts 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 after `bootstrap.packages` has installed the shell. It searches `PATH` first (never the mise shims directory), then common package-manager locations: `/opt/homebrew/bin`, `/usr/local/bin`, `/home/linuxbrew/.linuxbrew/bin`, `~/.linuxbrew/bin`, `/usr/bin` and `/bin`. If no executable is found, `mise bootstrap user apply` fails without changing `/etc/shells` or the account, and `status` reports `not installed`. `mise bootstrap user status` shows the path it found, and `--json` output has a new `resolved` field. Absolute paths work as before. [#14770](https://github.com/jdx/mise/pull/14770) (@jdx)

  ```toml
  [bootstrap.packages]
  "brew:fish" = "latest"

  [bootstrap.user]
  login_shell = "fish"
  ```

- **Doctor checks support templates.** `run`, `description` and `hint` in `[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. `dir` is not rendered. If a template fails, only that check reports `ERROR` and the other checks still run. Text that already contains `{{` or `{%` in these fields is now parsed as a template. [#14771](https://github.com/jdx/mise/pull/14771) (@jdx)

  ```toml
  [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. Unlike `pluginDirPath`, it is kept when the plugin is reinstalled. `mise cache clear` deletes it, so plugins must handle it being missing. It is `nil` when no cache directory is available. [#14776](https://github.com/jdx/mise/pull/14776) (@jdx)

- **`settings.write_targets` accepts relative and templated paths.** Before, only absolute or `~/` paths worked, so dotfiles shared between machines with different `MISE_CONFIG_DIR` or `XDG_CONFIG_HOME` had to hardcode the config directory. Templates are now rendered, and a relative result is joined to the `config_root` of 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, and `mise use --global` fails with "must resolve to an absolute path" instead of writing to the default file. [#14742](https://github.com/jdx/mise/pull/14742) (@jdx)

  ```toml
  # ~/.config/mise/conf.d/settings.toml
  [settings.write_targets]
  tools = "{{ config_source | dirname }}/tools.toml"
  ```

- **vfox backend plugins can report version aliases.** `BackendListVersions` can return an `aliases` table, so a registry's moving names resolve to real versions. Before, `"mise-ghcup:ghc" = "recommended"` passed the literal string to the plugin, installed into `installs/.../recommended`, never followed the tag when it moved, and showed `recommended` in `mise ls` and the lockfile. Now `ghc@recommended` resolves to `9.10.3`, installs into `9.10.3/`, and the lockfile records `9.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 the `aliases` field, so plugins can adopt it right away. [#14732](https://github.com/jdx/mise/pull/14732) (@jdx, @cprecioso)

  ```lua
  return {
    versions = { "9.8.4", "9.10.3", "9.14.1" },
    aliases = { recommended = "9.10.3" },
  }
  ```

- **`mise lock` and version listing work with a PyPI index that uses a keyring.** With an index such as Azure Artifacts and `artifacts-keyring`, `mise install` worked because uv read the password from its keyring provider, but mise's own requests sent no credentials and failed with `401 Unauthorized`. Set the new `pypi.keyring_provider` (`disabled` by default, or `subprocess`; env var `MISE_PYPI_KEYRING_PROVIDER`) and give the registry URL a username without a password. mise then runs `keyring get <url> <username>`, as uv does, and sends the result as HTTP Basic credentials when it lists versions and when `mise lock` reads a release's `requires-python`. The password is never written to the lockfile or the version cache. The `keyring` executable must be on `PATH`. If it is missing or has no entry, mise sends the request without credentials. [#14733](https://github.com/jdx/mise/pull/14733) (@jdx)

  ```toml
  [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 with `packslip plugin sources require the vfox plugin type`. An explicit `asdf:` or `package:` prefix is still rejected, and the error now says "vfox or vfox-backend". The archive still declares `extensions.mise.plugin = "vfox"`. [#14727](https://github.com/jdx/mise/pull/14727) (@jdx)

  ```sh
  mise plugins install vfox-backend:my-backend 'packslip:your-org/my-backend#1.0.0'
  ```

- `mise bootstrap packages list` is now an alias for `status`, alongside `ls`. [#14709](https://github.com/jdx/mise/pull/14709) (@himkt)

## Changed

- **`find_up` in `env._.file` starts at the config root.** Every other relative path in a config resolves against the config root, but `find_up` started at the current directory. A relative `find_up` path now starts at the config root and walks up through its parents, which suits a monorepo whose root `.env` applies to every package. An absolute path starts at its own directory. A global config that wants to search from wherever mise runs can use `cwd`. The `env_file` setting still discovers dotenv files up from the current directory and is unchanged. See Upgrading. [#14734](https://github.com/jdx/mise/pull/14734) (@jdx, @thernstig)

  ```toml
  # ~/.config/mise/config.toml
  [env]
  _.file = { path = "{{cwd}}/.env", find_up = true }
  ```

- **`allow_builds` approves 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 as `dtrace-provider`. For a package list, mise now turns that default list off, so only the packages you name build. `allow_builds = true` and the default with no option are unchanged. This updates the embedded aube to 2.8.0. [#14731](https://github.com/jdx/mise/pull/14731) (@jdx)

## Fixed

- `exec()` in `[env]` values, and other commands mise runs with the pre-activation environment, can now find commands in a `_.path` directory that was already on `PATH` before activation, even if the shell deduplicates `PATH` (for example zsh with `typeset -U path PATH`). [#14772](https://github.com/jdx/mise/pull/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 by `rsync -a`, `cp -p` or `touch -r`). On Unix, mise now also compares the file's ctime. Cache entries written by older versions are rehashed once. [#14778](https://github.com/jdx/mise/pull/14778) (@jdx)
- On macOS, `pipx:` installs no longer fail when `aqua:aws/aws-cli` is in the toolset. Because macOS filesystems ignore case by default, mise's lookup for `python` matched aws-cli's executable `Python` shared library and passed it to uv as the interpreter. This lookup now requires the exact case. [#14769](https://github.com/jdx/mise/pull/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](https://github.com/jdx/mise/pull/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-certs` 1.0.10, `rustls-webpki` 0.103.16). Builds that use the OS trust store are fixed by OS updates instead. [#14781](https://github.com/jdx/mise/pull/14781) (@jdx)
- A `[bootstrap.compose]` project no longer reports drift on every run when a service uses a `service:` reference such as `network_mode: service:vpn`. Compose hashes the reference as written but stamps the container with the hash of the resolved `container:<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 explicit `files` list in the project's config. Projects without one hash as before. [#14729](https://github.com/jdx/mise/pull/14729) (@jdx)
- vfox plugins now keep symlinks when they extract `.zip` archives returned by `PreInstall`. Before, entries such as `bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js` became regular files holding the link target, and the tool failed with `No such file or directory`. Links that resolve outside the install directory are still rejected. The `http:` backend and tarballs were not affected. [#14730](https://github.com/jdx/mise/pull/14730) (@jdx)
- `[daemons.<name>] task = "worker"` now finds a file task such as `.mise/tasks/worker.sh`, as `mise run worker` does. Before, it failed with `daemon srv runs unknown task "worker"` unless you wrote `worker.sh`. [#14741](https://github.com/jdx/mise/pull/14741) (@jdx)
- On Windows, `mise self-update` no longer replaces the winget `mise-shim.exe` link with a copy. It updates the real `mise-shim.exe` next to the real `mise.exe`. Before, `mise reshim` kept warning that the shim was older than mise. [#14737](https://github.com/jdx/mise/pull/14737) (@jdx)
- An inherited `[dotfiles]` entry turned off with only `enabled = false` no longer prints `no recognized operation (block, source, or line), ignoring entry` on every command. Other tables with no recognized operation, such as a misspelled `line`, still warn. [#14726](https://github.com/jdx/mise/pull/14726) (@jdx, @ponko2)

## Upgrading

- **`find_up` in a global config:** a relative `find_up` entry in `env._.file` used 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_up` was added in 2026.10.12.

## Documentation

- The dotfiles docs now explain that a group entry with only `permissions` does not inherit the group's `mode`, so it deploys nothing. Removing `mode` from an entry that used to deploy a file leaves that file orphaned, and `--prune` deletes it. To deploy a file and set its permissions, use `{ mode = "copy", permissions = "0600" }`. [#14768](https://github.com/jdx/mise/pull/14768) (@jdx)

## New Contributors

* @Huge made their first contribution in [#14752](https://github.com/jdx/mise/pull/14752)

**Full Changelog**: https://github.com/jdx/mise/compare/v2026.10.15...v2026.10.17

## 💚 Sponsor mise

mise is built and maintained by [@jdx](https://github.com/jdx), an open source developer at [**entire.io**](https://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](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

Related Repositories