I have been using chruby1 for about 2 years. But when I started in the team I choose chruby because people on my team were using it. Emacs integration was terrible, because of the way chruby works you need a plugin. The other viable option seemed to be rbenv2, which was widely used but I had only ever seen it installed on server. I did always think that it's model of using shims was a bit better than chruby that updates the PATH. The shim model provides better integration with any program that generates sub-processes, this because the complexity of managing the interpreters is done with these wrapper shims instead of burdening the program with being aware beyond just adding it to your path, you just need to execute from within the context of the project.

Gradually though, as I started contributing to more projects, and needed more interpreters, it seemed like the list kept growing. Pyenv3 was used for python in a previous job, and then when working in TypeScript, NVM4 was the obvious choice. It's amazing how each language seemed to be solving the same problems over and over.

It's a bad sign when there is so much duplication of effort configuring all the alternative implementations, but they worked for the most part. What made me consider looking for alternatives for was noticed my shell was taking an unreasonable time at start. Maybe 4 seconds to get to the prompt, long enough for me to even think it could be DNS.

Profiling the evaluation of zshrc is fortunately pretty easy to do. ZSH has a module that can help with profiling called zprof it can be used like this.

zmodload zsh/zprof
# ... the code you want to profile

It outputs a whole lot of timing information, and bam. NVM, there it was, it was most of my startup time. There was an outstanding bug5 it had already been open a few months, so that was what got me looking for alternatives.


I can't remember how I came across ASDF-VM6, it was probably on a news site. But it's a real game changer.

The 2 killer features, are.

  1. it's a universal interface
  2. using the shim model

The universal interface is great because it supports more than one ecosystem's tools, I have so far used this for terrafrom, python, nodejs, ruby. Just not having to hunt around for the local languages tool to do this one thing is very helpful.

The shim model is compatible by default with all IDE's and tools, which is great because having to install an associated plugin into every tool to make it work is not really scale able

Listing Plugins

Each language is expressed as a plugin, so you install a plugin for the interpreters you want to be able to set the version of.

asdf plugin list all
1password-cli                 https://github.com/NeoHsu/asdf-1password-cli.git
act                           https://github.com/grimoh/asdf-act.git
actionlint                    https://github.com/crazy-matt/asdf-actionlint.git
action-validator              https://github.com/mpalmer/action-validator.git
adr-tools                     https://gitlab.com/td7x/asdf/adr-tools.git
ag                            https://github.com/koketani/asdf-ag.git
age                           https://github.com/threkk/asdf-age

It reminds me a bit of how on supercomputer setups we used a tool called Modules7. We used it in HPC because we wanted to provide multiple versions of all software and libraries.

Listing tool versions

ASDF acts in a similar way for lots of tools which is why you see a list of programs that aren't actually interpreters or compilers in the list above. Once a plugin is installed you can then list all the possible version of it to install.

asdf list all nodejs

Installing a specific tool version

Once you know the available versions you can install a specific one, with an install command.

asdf install nodejs 20.4.0

Setting default packages to install

Another cool features is the possibility to install some global packages

for example my ~/.default-gems contains


Default tool versions

This gives me some gems by default with each version of the interpreter.

Finally, there is a ~/.tool-versions file that containers the default interpreter to use if there is no project specified version.

python 3.9.5
nodejs 18.16.1
ruby 2.7.1


Copyright © 2019-2023 Russell Sim (rsl@simopolis.xyz).

Author: Russell Sim (Mastodon | SourceHut | Github) Date: 2020-07-15 Wed 00:00 Emacs 29.0.91 (Org mode 9.6.5)