Config hierarchy & precedence¶
Two teammates clone the same repo at the same commit and get two different node versions — no
error, no warning, on either machine. The committed mise.toml is identical; what differs is
everything mise layers on top of it before resolving a version. The course page's mental-model
diagram (§2) names "Config loading" as one box — mise.toml · mise.local.toml · .tool-versions ·
global config — without saying which one wins when two of them disagree; this page slows down on
exactly that: the precedence order, why it's a merge across a directory walk-up and not a single
winner-takes-all file, and how to ask mise where a resolved value actually came from.
TL;DR — in 30 seconds:
- Six sources contribute to a resolved value, from lowest to highest precedence: global config →
project
mise.toml→.tool-versions→mise.local.toml→MISE_<TOOL>_VERSIONenv vars → CLI flags. - It's a merge across the directory walk-up, not "nearest file wins" — a key set only in a parent
directory's
mise.tomlstill applies to every subdirectory that doesn't override it. - An untrusted
mise.tomlcontributes nothing at all — trust gates the whole precedence chain, so check trust state before reasoning about which file wins.
1. The sources, lowest precedence to highest¶
Six places can contribute to "what version of node, what env var, what task exists right now":
| Source | Scope | Typically committed? |
|---|---|---|
Global config — ~/.config/mise/config.toml |
Whole machine, every project without its own override | No — per-machine |
Project config — mise.toml in the current directory and every parent directory, walking up |
One repo (or a subtree of it) | Yes |
.tool-versions |
Legacy asdf-format file, read for compatibility | Yes (asdf-era repos) |
Local overrides — mise.local.toml |
Same directory as mise.toml, gitignored |
No — personal |
Environment variables — MISE_<TOOL>_VERSION style |
Current shell session | No — ephemeral |
CLI flags / mise exec |
A single command invocation | No — one-off |
Read top to bottom as increasing precedence: a global default is the fallback everyone gets; a
one-off CLI flag on a single command beats everything else, for that command only. .tool-versions
sits below mise.toml — it exists so an asdf-era repo keeps working, not as a way to override a
mise.toml that already covers the same tool. That's also where the two-teammates gap from the top
of this page usually comes from: mise.local.toml and a MISE_<TOOL>_VERSION env var both outrank
mise.toml in that table, and neither one shows up in git diff — a clean diff is no guarantee the
resolved version matches.
2. It's a merge across the directory walk-up, not a single winner¶
The easy mistake: assuming mise finds the nearest mise.toml and stops there, ignoring everything
above it. It doesn't. Starting from the current directory and walking up to the filesystem root, mise
merges every mise.toml (and mise.local.toml) it finds:
- For a key defined at more than one level (say, both the repo root and a subdirectory both pin
node), the closer directory wins. - For a key defined only further up (a repo-root
[env]var with no override in the subdirectory), it still applies — the subdirectory inherits it.
flowchart TD
Root["Repo root mise.toml<br/>node = 20 · [env] FOO=bar"]
Sub["Subdirectory mise.toml<br/>node = 18 (no [env] override)"]
Root -->|closer directory wins for node| Eff
Root -.->|inherited, not overridden| Eff
Sub --> Eff["Effective config in subdirectory<br/>node = 18 · FOO=bar"]
This is the same layering course.md §8 describes for [env] ("a child mise.toml's [env] overrides
its parent's") — it's not special to env vars, it's how every section ([tools], [env], [tasks],
[vars]) resolves.
3. Trust gates the whole chain¶
Precedence only matters among files mise actually reads. An untrusted mise.toml contributes
nothing — not "low priority," genuinely ignored, [tools]/[env]/[tasks]/[hooks] and all — until
mise trust (course.md §10). So the practical order of operations is: trust first, then precedence
decides which trusted file's value wins.
4. Diagnosing where a value came from¶
Three commands answer "why is this the version I'm getting?" at increasing levels of detail:
mise current # resolved versions, no explanation of source
mise config ls # every config file mise loaded here, in precedence order
mise doctor # config paths, trust state, activation health
mise config ls is the one to reach for when a version doesn't match what you expected: it lists the
files mise actually merged for the current directory, in the order they were applied — the fastest way
to confirm whether a stray mise.local.toml or a parent directory's mise.toml is the culprit.
flowchart BT
G["Global config<br/>~/.config/mise/config.toml"] --> M
T[".tool-versions<br/>(legacy, asdf-compat)"] --> M
P["Project mise.toml<br/>(cwd + each parent dir, merged)"] --> M
L["mise.local.toml<br/>(gitignored, per-machine)"] --> M["Resolved config<br/>tools · env · tasks · vars"]
E["MISE_<TOOL>_VERSION<br/>env vars"] --> M
C["CLI flags / mise exec<br/>(single invocation)"] --> M
M --> R["Effective value<br/>(what mise current shows)"]
Precedence increases bottom-to-top of the diagram's inputs — global config is the widest net, a CLI flag is the narrowest and always wins for the command it's attached to.
Common gotchas
- Trusting a clean
git diffonmise.tomlas proof the resolved version matches. Fix: it isn't —mise.local.tomlandMISE_<TOOL>_VERSIONenv vars both outrankmise.tomland never show up in a diff; runmise config lsto see every source that actually contributed. - Assuming the nearest
mise.tomlwins and a parent directory's file is ignored. Fix: it's a merge across the whole directory walk-up, not a single winner — a key set only in a parent still applies to every subdirectory that doesn't override it. - Forgetting that an untrusted
mise.tomlcontributes nothing at all. Fix: it isn't "low priority," it's genuinely ignored — every section,[tools]/[env]/[tasks]/[hooks]— untilmise trustruns; check trust state before reasoning about precedence. - Guessing which file set a value instead of checking. Fix:
mise config lslists every config file mise merged for the current directory, in precedence order — always confirm with it rather than asserting from memory.
Check yourself — you clone a repo, git diff on mise.toml is clean, but mise current shows a different node version than a teammate's machine. What could explain this, and which command would confirm it?
A gitignored mise.local.toml override or a MISE_NODE_VERSION env var set in one shell — both
outrank mise.toml and never enter git diff. mise config ls lists every file mise actually merged
for the directory, in precedence order, and would surface the extra source.
Check yourself — a subdirectory has its own mise.toml that only sets [tasks]. Does it inherit the [tools] versions pinned in the repo-root mise.toml, or does defining its own file replace the parent entirely?
It inherits. mise merges every mise.toml found while walking up from the current directory to the
root — a key defined only in a parent (like the root's [tools]) still applies wherever nothing closer
overrides it; defining a file doesn't erase the parent's other keys.
You can defend this when you can name, for a given tool version or env var, which of the six
sources set it; explain why a subdirectory's mise.toml doesn't erase its parent's other keys; and use
mise config ls to confirm a precedence guess instead of asserting it from memory.
Go deeper: the complete configuration hierarchy — every source mise reads and the full merge behavior — is documented in the mise configuration docs; this page covers the 80/20 you need for the Do exercise.
5. How this connects¶
- §1's ladder is the same "later, more specific layer wins" merge shape as two other modules' layering
pages, which already name this page: Helmfile's environments & values precedence
(module 06, Helmfile, §5) and Ansible's variable precedence ladder
(module 08, Ansible,
01-inventory-and-vars.md§4) — three different tools, the same rule. - §2's directory walk-up merge isn't specific to
[env]— this course's next page, Tasks (§5), shows the identical merge deciding which[tasks]a subdirectory sees, letting a repo-rootcitask and a service-leveltesttask coexist without either file knowing about the other. - Don't conflate this page's merge with this course's third page, Environments & backends (§1): that
page's
MISE_ENVprofile switch changes which additionalmise.<profile>.tomlfile gets merged in on top of the base config — a different axis from this page's directory walk-up, which decides which directory's files get merged — the two "environment" concepts answer different questions.