Skip to content

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, replaces 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", 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.toml does nothing until you mise trust it — 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.toml and explain why that kills "works on my machine."
  • Set per-directory env and define tasks.
  • Describe the CI install pattern.

Do it with Claude

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 coursefocus §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 full mise.toml schema, 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.toml makes 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 diff on mise.toml as proof of the resolved version. Fix: run mise config lsmise.local.toml and a MISE_<TOOL>_VERSION env var both outrank the committed file and never show up in a diff.
  • Expecting .nvmrc or .python-version to be picked up automatically. Fix: mise does not read those files — convert to mise.toml (or, for asdf-era repos, .tool-versions, which mise does read for compatibility).
  • Forgetting mise trust after editing [tools]/[env]/[tasks]/[hooks]. Fix: any change to a trusted config re-asks for trust — the edit is silently inert until you mise trust again, 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:

  1. Install mise first (course §3): brew install mise, then activate it in your shell (see §3).
  2. In a scratch repo, write a mise.toml pinning 2–3 tools to exact versions, plus a [env] var and a [tasks] entry.
  3. mise install, then mise run <task> and confirm the env var is set inside it.
  4. 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.

  1. Walkthrough: Explain your mise.toml — each tool pin, the env, the task.
  2. Alternative: Exact pins vs ranges (node = "20" vs "20.11.1") — the trade-off, and which for a team?
  3. 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?
  4. Provenance: Which of the mise.toml did 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.toml replaces 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 diff on mise.toml doesn't prove what version actually resolved — mise.local.toml and a MISE_<TOOL>_VERSION env var both outrank the committed file and are gitignored/ephemeral, so run mise config ls to see everything mise actually merged, in precedence order.
  • mise does not read .nvmrc or .python-version automatically — convert to mise.toml (or .tool-versions for asdf-era repos, which mise does read for compatibility).
  • A mise.toml is inert until you mise trust it — 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.toml schema, version specifiers, and CLI. Free.
  • Docs · Comparison to asdf — jdx. Why mise replaces asdf — no shims, PATH-based activation, and .tool-versions compatibility. 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: