I first tried mise while setting up Arch Linux on my Dell XPS 13.
I was bringing over my Doom Emacs configuration, which used asdf.el, and
considering how mise would fit into the same environment.
Colleagues had already spoken well of it. What interested me was the possibility of simplifying runtime integration in both Fish and Emacs. If that worked on the Dell, I was considering the same change on my Mac.
The useful test was inside the editor. I set up separate directories for Ruby 3.4 and 3.3, opened their Ruby files in Doom, and checked the version from each buffer. Each one used the Ruby selected for its directory. I also confirmed that Doom could find Node and npm, and that the Elixir tools were available.
That gave me a working setup on Arch before moving my main Mac environment away from asdf. The Mac work then extended into Fish configuration and the tools Emacs launches. The migration described here took place in October 2026; the Mac checks used mise 2026.10.21.
Keeping project version files
The starting point on the Mac was a set of projects with existing version files. Replacing the manager did not require changing every project’s Ruby or converting all those files to a new format.
Mise reads .tool-versions, the format I was already
using with asdf. For ecosystem-specific files such as .ruby-version and
.nvmrc, I enabled the corresponding tools in the global settings:
[settings]
idiomatic_version_file_enable_tools = ["ruby", "node"]
experimental = true
The experimental setting is used by the installed mise.el, which
checks it before enabling the integration.
My personal defaults now live in ~/.config/mise/config.toml. Its current
tool selections include changes made during the wider cleanup:
[tools]
elixir = "1.20.2-otp-29"
go = "1.27"
ruby = "4.0.9"
rust = "1.99.1"
Those are personal defaults. A project’s files can select another version.
During the migration checks in the blog repository, mise current ruby
reported 3.4.6. The project’s .tool-versions selected that version even
though its .ruby-version said 3.4.5.
The configuration rules determine how global and project
files combine. I checked the effective selection rather than assuming
that finding a familiar version file was enough. mise config ls lists
the configuration files being read, and mise current ruby reports the
selected Ruby.
I manage my dotfiles with GNU Stow. During the transition, the package
still named asdf retained .tool-versions, .default-gems and
.default-mix-commands. I later moved those files into the mise package
and recreated their Stow links. The move left the effective runtime
selections unchanged. That was configuration housekeeping, separate from
moving or installing runtimes.
The default-installation files were retained, but I have not validated that a fresh Ruby or Elixir installation consumes them automatically.
Letting Fish activate mise
On Arch, the first mise doctor check reported that mise was not activated.
After setting up Fish activation, it reported no problem. Installing the
executable had been one step; arranging the shell environment was another.
On the Mac, mise is installed through Homebrew. I did not need to add an
activation line to my own config.fish: this installation supplies
/opt/homebrew/share/fish/vendor_conf.d/mise-activate.fish:
if [ "$MISE_FISH_AUTO_ACTIVATE" != "0" ]
/opt/homebrew/opt/mise/bin/mise activate fish | source
end
The Fish setup documentation describes this package-provided activation. I kept that mechanism instead of adding another copy to my personal configuration.
The surrounding Fish changes made the command paths explicit.
Personal paths moved into conf.d/00-environment.fish, which normalizes
the inherited PATH, removes duplicates and filters known obsolete
entries, including the old asdf shims. Asdf and older installations were
cleaned up as part of that work.
The prompt also needed to follow the new manager. My old override parsed
.tool-versions itself. Its replacement asks mise current once per render
and displays the languages relevant to the directory. Ruby, Node and
Elixir were joined by Rust, detected through files such as Cargo.toml.
The prompt now uses mise’s selection rather than maintaining its own
version-file parser.
Rustup remains in this setup: the mise Rust entry links to the existing
~/.cargo/bin directory. I added Rust to the same prompt segment rather
than adding another standalone version check.
The wider Fish review also removed the old Oh My Fish Emacs plugin and
kept the command helpers I use as autoloaded functions. That removed an
emacs --version call from shell initialization. I did not
measure a before-and-after startup time for it.
The environment inside Doom
The Doom change had two parts. I replaced the asdf.el package declaration
with the recipe for mise.el in packages.el:
(package! mise :recipe (:host github :repo "eki3z/mise.el"))
In config.org, I replaced the asdf-enable setup with:
(use-package! mise
:config
(global-mise-mode +1))
On Arch, after synchronizing Doom, I confirmed that the mode was active and that Emacs could find the Ruby shim. I checked Ruby LSP and ElixirLS in that setup.
The installed revision of mise.el makes the mechanism visible.
It calls mise env --json, merges the result into a buffer-local
process-environment, and derives a buffer-local exec-path from the
resulting PATH.
Emacs uses exec-path to find a program named without an absolute path.
The process it starts inherits process-environment, including the PATH
used to find its own child commands. The Emacs process reference
describes both roles. That is why checking Ruby in a terminal alone could
not validate what a formatter or language server launched by Emacs used.
The package uses inheritenv around selected commands, including
shell-command-to-string, so they can carry the editing buffer’s
environment into temporary buffers.
Doom has its own environment snapshot as well. In this revision,
doom sync regenerates an existing snapshot;
doom sync --env also creates one if absent. The Emacs command
doom/reload-env only loads the saved file into the
running session. On Arch, doom doctor continued to warn that my PATH
had changed even while the runtime checks worked. I did not establish the
cause of that warning.
On the Mac, my Ruby formatter configuration also follows the project:
RuboCop when a .rubocop.yml is present, otherwise Standard. RuboCop uses
bundle exec when there is a Gemfile. Both commands clear inherited
Bundler-related variables before starting, and Standard uses the gems
available to the selected Ruby. The full configuration
contains the details; the important connection here is that a formatter
needs the project’s environment as well as its configuration file.
Following the development tools
Some settings outside the runtime manager still named the old locations. My Arduino configuration was one of them:
(defvar bounga/arduino-language-server
- (expand-file-name "~/go/bin/arduino-language-server"))
+ (executable-find "arduino-language-server"))
The defvar initializer does not repeat the lookup each time I open an
Arduino buffer. Resolving it to a mise shim gives that saved value a stable
path while the executable lives under a particular Go installation.
Checking the running Emacs confirmed the shim path, and the server answered
its help command.
An installation problem with arduino-language-server and dockfmt led me
to check Go. Mise had Go 1.27.3 installed, but it was inactive in the directory
where I was working, so ordinary commands still found Homebrew’s Go 1.26.6.
mise exec go@1.27.3 -- go version used the intended installation, and both
tools built with it in a temporary directory. I then selected Go globally
and installed them.
mise install does not record a version selection;
mise use records one and installs the runtime if needed. I did not reproduce
the original installation error, so the inactive Go is an observed problem,
not a demonstrated cause of that failure.
This setup puts Go-installed executables in the selected Go version’s
bin directory through GOBIN. It changes where the tools live.
After adding an executable there, mise reshim makes it
available through the shim directory.
That mattered for dockerfmt, which Doom actually uses for Dockerfiles.
I had a copy installed with Homebrew, and Emacs kept finding it after I
installed dockerfmt with the mise Go. After mise reshim, executable-find
resolved to the mise shim. The earlier dockfmt build concerned a different
formatter. Doom’s Docker configuration uses dockerfmt,
although its doctor check still looks for dockfmt.
Gems outside the checkout
I made a separate storage change during the cleanup: removing the global
Bundler path setting that sent gems into vendor/bundle. Projects using
the same Ruby installation can now share installed gems.
Mise selects Ruby and, in this setup, its RubyGems directory. Each project’s
Gemfile and lockfile still select its dependencies. With Ruby 3.4.6,
Gem.dir matches Bundler.bundle_path under both Bundler 2.3.17 for the
blog and 4.0.17 for ctf_tracker.
That follows the Bundler policy, not a mise feature. The
configuration reference specifies Gem.dir as the default
before Bundler 5, and a repository-relative .bundle directory starting with
Bundler 5. A project can override path.
Seven active projects passed dependency and principal-library load checks
against the shared directories, with their Gemfiles, lockfiles and Ruby
selection files unchanged. After cleanup, ctf_tracker also passed its 86
examples. The global automatic-installation and cleanup settings were
removed, along with a production exclusion that excluded no dependencies
in those projects. My .gemrc now only disables documentation
and backtraces, with its Stow link preserved.
The 45 removed vendor/bundle trees had an allocated size of about 10.46 GiB
in the retained du -sk inventories. Gems were also installed in the shared
directories, so this is not a measured net saving. With those trees removed
from the checkouts, I re-enabled LSP file watchers. After restarting Emacs,
a large Rails project opened without a warning or noticeable slowness, an
observation limited to that project.
The two Ruby buffers on Arch were an early check that per-directory selection could work inside the editor. On the Mac, getting there also meant replacing old paths and checking the tools launched by the editor. The project dependency files could stay as they were while I changed the runtime manager and the environment around them.
Comments, questions, or just a reaction?
Send an email to ~bounga/bounga.org-discuss@lists.sr.ht. It is public and archived, so other readers can follow along and answer too.