mise¶
Tool · prereq 01 (Foundations) · ~half a day
Node works on your machine and breaks in CI — not because the code changed, but because your machine has nvm's default Node, CI has whatever the base image shipped, and nothing in the repo says which one is correct. Multiply that by every language a repo touches and "works on my machine" stops being a joke. mise is one binary that replaces the whole pile of per-language version managers (nvm, pyenv, asdf, and friends): a single mise.toml pins every tool version, sets per-directory env vars, and defines the tasks the repo runs — resolved the same way on your laptop, a teammate's, and CI.
TL;DR — in 30 seconds:
- One file,
mise.toml, replacesnvm/pyenv/asdf/direnv/just: it pins tool versions, sets per-directory env vars, and defines tasks — resolved the same way on every machine and in CI. - Always pin exactly (
node = "20.11.0", never"20"or"latest") in any config you commit — mise has no lock file, so a loose specifier is what actually drifts between machines. - A
mise.tomldoes nothing until youmise trustit — cloning a repo with an untrusted config is safe;[tools]/[env]/[tasks]/[hooks]are all inert until you explicitly trust the file.
Learning objectives¶
By the end, the junior can:
- Pin per-project tool versions in
mise.tomland explain why that kills "works on my machine." - Set per-directory env and define tasks.
- Describe the CI install pattern.
Pair, Do and Prove run in Claude Code, with the onboarding-tutor skill. Click a button to copy the exact prompt, then paste it into Claude Code in this repo.
1. Learn · acquire the concept¶
Read: the mise course — focus §4 (minimum viable
mise.toml), §5 (installing tools), §6 (version specifiers — get this right), §8 (per-dir env), §9 (tasks). Skip for now: §7 (backends in depth), §12 (CI) until you've used it locally. Glossary (beat 5): the module’s Anki deck Read next: mise documentation (official docs — the fullmise.tomlschema, version-specifier syntax, and CLI reference).
Check yourself — a shared mise.toml pins node = \"20\". Two teammates clone the repo the same day and end up with different patch versions of Node. Why, and how should the entry have been written?
"20" resolves to the latest 20.x patch at install time — whoever ran mise install first (or on
an older mirror) gets whatever patch was newest then, and a teammate installing later gets a newer one.
The fix is to always pin exactly in any config you commit: node = "20.11.0", not "20" — mise has no
lock file, so the string in mise.toml is the only thing keeping every machine identical.
Check yourself — mise current shows a python version you didn't expect for this directory, and git diff on mise.toml is clean. Where do you look, and why doesn't a clean diff rule this out?
Run mise config ls to see every config file mise actually merged for this directory, in precedence
order. A clean git diff doesn't rule anything out because the two highest-precedence sources —
mise.local.toml and a MISE_<TOOL>_VERSION env var — are both gitignored/ephemeral and never show up
in a diff, yet both outrank the committed mise.toml.
Check yourself — you clone a repo with a mise.toml that defines [tasks] and [env]. Does anything run or load automatically on cd into it?
No — an untrusted mise.toml is inert. mise refuses to read [tools]/[env]/[tasks]/[hooks] from
any config until you run mise trust (or it falls under MISE_TRUSTED_CONFIG_PATHS). This is what
stops a malicious PR that adds a [hooks] block from running anything until a human explicitly trusts
the changed file.
2. Pair · passive → active¶
Drive the onboarding-tutor skill:
- "Explain how mise replaces nvm/pyenv/asdf in one binary."
- "3-line mental model of how
mise.tomlmakes two machines identical." - "Quiz me on version specifiers — where does drift sneak in?"
Common gotchas
- Pinning
node = "20"or"latest"in a config you commit. Fix: always pin exactly ("20.11.0") in shared config — mise has no lock file, so a loose specifier is what actually drifts between machines and CI runs, not a defense against drift. - Trusting a clean
git diffonmise.tomlas proof of the resolved version. Fix: runmise config ls—mise.local.tomland aMISE_<TOOL>_VERSIONenv var both outrank the committed file and never show up in a diff. - Expecting
.nvmrcor.python-versionto be picked up automatically. Fix: mise does not read those files — convert tomise.toml(or, for asdf-era repos,.tool-versions, which mise does read for compatibility). - Forgetting
mise trustafter editing[tools]/[env]/[tasks]/[hooks]. Fix: any change to a trusted config re-asks for trust — the edit is silently inert until youmise trustagain, even though the file looks correct.
Done when: you can explain why loose version specifiers cause drift, unprompted.
3. Do · produce an artifact¶
Exercise — pin a polyglot repo:
- Install mise first (course §3):
brew install mise, then activate it in your shell (see §3). - In a scratch repo, write a
mise.tomlpinning 2–3 tools to exact versions, plus a[env]var and a[tasks]entry. mise install, thenmise run <task>and confirm the env var is set inside it.- Explain (or demonstrate) how a fresh machine / CI would get the exact same versions from this file.
Done when: mise install pins the tools, the task runs with the env applied.
4. Prove · understanding gate¶
Mandatory. Pass bar in the Socratic gate.
- Walkthrough: Explain your
mise.toml— each tool pin, the env, the task. - Alternative: Exact pins vs ranges (
node = "20"vs"20.11.1") — the trade-off, and which for a team? - Failure & debug: A teammate gets a different tool version than you despite the same repo. What likely differs, and how do you make it deterministic?
- Provenance: Which of the
mise.tomldid the agent write? How did you verify the pinned versions actually installed (not just that the file looked right)?
Pass bar: you can defend the pins and explain drift. "It worked on mine" without understanding determinism = back to Pair.
5. Retain · fight forgetting¶
Study the module deck SE Onboarding::11 mise — its 10 theme subdecks let you drill one area at a time. It syncs to your Anki automatically on pull (anki-sync).
Done when: you can recall — from the deck — mise.toml, version specifier, per-dir env, tasks, and trust.
Key takeaways¶
- One
mise.tomlreplaces the whole pile of per-language version managers (nvm,pyenv,asdf,direnv,just): it pins tool versions, sets per-directory env vars, and defines tasks — resolved the same way on every machine and in CI. - Always pin exactly (
node = "20.11.0") in any config you commit — a loose specifier like"20"resolves to the latest matching version at install time, so different machines installing on different days end up with different patch versions. mise has no lock file, so the pin string is the only thing keeping machines identical. - A clean
git diffonmise.tomldoesn't prove what version actually resolved —mise.local.tomland aMISE_<TOOL>_VERSIONenv var both outrank the committed file and are gitignored/ephemeral, so runmise config lsto see everything mise actually merged, in precedence order. - mise does not read
.nvmrcor.python-versionautomatically — convert tomise.toml(or.tool-versionsfor asdf-era repos, which mise does read for compatibility). - A
mise.tomlis inert until youmise trustit — cloning a repo with an untrusted config is safe;[tools]/[env]/[tasks]/[hooks]don't load or run until you explicitly trust the file, and any further edit to a trusted config re-asks for trust.
Go deeper — curated resources¶
Hand-picked external material to take this topic further — the best books, courses, talks, and writing. Cost is tagged Free, Free online (full text/course free on the web), or Paid.
- Docs · mise — jdx. The tool's own guide and the source of truth for the
mise.tomlschema, version specifiers, and CLI. Free. - Docs · Comparison to asdf — jdx. Why mise replaces asdf — no shims,
PATH-based activation, and.tool-versionscompatibility. Free. - Blog · About mise-en-place — Jeff Dickey (jdx). The reasoning behind unifying tool versions, env vars, and tasks in a single binary. Free.
Gate log (mentor fills on pass)¶
- Date passed: / Passed by: / checked by: / Weak spots: