One tool, every runtime version
Tools such as mise and asdf can manage several runtime versions through one interface. They do not automatically replace environment managers, package ecosystems, or deployment images that a project already owns.
Install mise
brew install mise
echo 'eval "$(mise activate zsh)"' >> ~/.zshrcPin versions per project
# In any project root
mise use python@3.12 node@20 ruby@3.3
# Writes .mise.toml with the pins
cat .mise.toml
# Anyone cd-ing in gets the right versions automatically.mise.toml can be the version declaration when the repository chooses mise as its owner. Activation still depends on shell integration or explicit execution, and teams must reconcile existing Conda, system, container, or CI ownership before adding another layer.
Global tool defaults
mise use -g python@3.12 node@22
mise list # see what's installed
mise upgrade # bump pinned versions to latest patchWhy mise over asdf
- A compiled implementation can make common operations responsive; measure the commands and plugin path that matter to this project.
- Compatible with asdf plugins out of the box.
- Built-in env var management (
[env]in mise.toml). - direnv-style hooks without needing a second tool.
If you're starting fresh, mise. If you have an asdf setup that works, no rush — both are healthy projects.
One runtime owner per project
Preserve the repository's declared manager unless there is an explicit migration plan. Coexistence on one machine is possible; stacking multiple activators inside one project makes PATH and interpreter selection harder to explain.
A version pin is only one layer of reproducibility
Dependency locks, native libraries, environment values, and the operating system can differ even with the same Python or Node version. Know the boundary the runtime manager actually solves.
Automatic activation changes command resolution
Entering a directory can rewrite PATH. Review trust prompts and configuration before approving an unfamiliar repository, then inspect the selected executable and version.