The shim is what makes dvm invisible. With it on your PATH, plain dart follows your pins — and so does every tool that spawns
dart behind your back.
What the shim is#
dvm setup writes exactly one file per platform. On macOS and Linux it is a two-line POSIX shell script:
#!/bin/sh
exec /Users/you/.dvm/bin/dvm exec dart "$@"
exec replaces the shell process rather than spawning a child, so the only cost of the indirection is starting
/bin/sh plus dvm's own AOT startup — and then the real dart inherits the terminal, the exit code, and the signals directly. From there on you are talking to the SDK.
On Windows it is ~/.dvm/shims/dart.bat, doing the same thing:
@echo off
"C:\Users\you\.dvm\bin\dvm.exe" exec dart %*
Windows resolves a bare dart to it through PATHEXT, so cmd.exe
and PowerShell both find it, and the batch file's exit code is the SDK's.
That is the whole mechanism.
dvm setup
Wrote /Users/you/.dvm/shims/dart
-> /Users/you/.dvm/bin/dvm exec dart
Add this to ~/.zshrc:
export PATH="/Users/you/.dvm/shims:$PATH"
Then check it with: dvm doctor
Adding the PATH line#
dvm setup prints the exact line and names the file it belongs in, so the change to your login shell stays yours to make. The right file differs between shells, between login and non-login setups, and between hand-managed profiles, and you are the one who knows which of those you have.
dvm setup reads $SHELL to work out which line to hand you and where it goes:
| Shell | Where the line goes |
|---|---|
| zsh (macOS default) | ~/.zshrc |
| bash | ~/.bashrc, or ~/.bash_profile on macOS |
| fish | ~/.config/fish/config.fish, with fish_add_path |
| PowerShell | your user environment — see On Windows below |
Then start a new shell. The file is read when a shell starts, so the one you are in keeps the environment it was given.
Letting dvm add the line#
When you would rather dvm made the edit, ask for it:
dvm setup --write-path-line
Wrote /Users/you/.dvm/shims/dart
-> /Users/you/.dvm/bin/dvm exec dart
Backed up /Users/you/.zshrc -> /Users/you/.zshrc.dvm-backup-20260829-154646
Added this line to /Users/you/.zshrc:
export PATH="/Users/you/.dvm/shims:$PATH"
It takes effect in shells started after this. For the one you are in: source /Users/you/.zshrc
Undo it with: dvm setup --remove-path-line
Then check it with: dvm doctor
It copies the file to a timestamped backup first, and writes the line inside markers so it stays identifiable:
# >>> dvm >>>
export PATH="/Users/you/.dvm/shims:$PATH"
# <<< dvm <<<
Run it twice and the second run finds the line and adds nothing — including a line you typed yourself in different quoting, or with
$HOME in place of your home directory. dvm setup --remove-path-line takes dvm's own block back out and leaves the shims in place; a line dvm did not write is reported and left exactly as you have it.
It stays opt-in because the reasoning above is still yours: dvm writes to one file, and you are the one who knows which file your login shell actually reads.
On Windows#
PowerShell takes PATH from your user environment rather than from a startup file, so dvm setup
hands you the command that sets it:
Run this once in PowerShell:
[Environment]::SetEnvironmentVariable('Path', 'C:\Users\you\.dvm\shims;' + [Environment]::GetEnvironmentVariable('Path', 'User'), 'User')
That edits your user PATH, so it survives a reboot. It has to go ahead of anything else that puts a dart on PATH, and it only takes effect in terminals opened after you run it.
For the terminal you are in right now:
$env:Path = 'C:\Users\you\.dvm\shims;' + $env:Path
It writes to the User scope, so it needs no elevation and applies to every terminal you open afterwards.
dvm doctor hands you the same line when it finds the shims missing from
PATH.
Git Bash and MSYS set $SHELL, so dvm gives them the POSIX export line and the startup file that shell reads — the shell you are actually in is the one dvm answers for.
Why the shims directory goes first#
PATH is searched left to right and the first match wins, so ~/.dvm/shims belongs
ahead of every other directory that supplies a dart.
That position is the whole of it. If Homebrew's dart, or a Flutter SDK's bundled dart, or
/usr/local/bin/dart appears earlier in the list, that binary is found first and the shim is never reached. Your pins do nothing, and there is no error message — the symptom is
dart --version reporting a version you did not ask for, with everything else apparently correct.
dvm doctor checks exactly this, and reports the offending entry by path:
dvm doctor
The shadowing shell function#
This is the one that costs people an afternoon, and it is why doctor exists.
The older cbracken/dvm is installed as a shell function
sourced from your .zshrc or .bashrc. A shell function is resolved before
PATH is searched at all. So on a machine that has ever had that tool installed, typing
dvm runs the old function, the new binary is never reached, and nothing on screen says why — the commands are similar enough that the errors look like your own mistakes.
dvm doctor reads your shell startup files and reports it by file and line. The fix is to delete the line that sources the old tool, then start a new shell.
Verifying it worked#
dvm doctor
dvm doctor
ok PATH: /Users/you/.dvm/shims is on PATH ahead of every other dart.
ok shims: /Users/you/.dvm/shims/dart runs /Users/you/.dvm/bin/dvm.
ok shell: no shell function or alias named `dvm` in your startup files.
ok config: /Users/you/.dvm/config.json is valid; the global default Dart 3.9.0 is installed.
ok project: /Users/you/code/my-project/.dvmrc pins Dart 3.13.2, which is installed.
ok project: /Users/you/code/my-project/.dvm/dart_sdk points at /Users/you/.dvm/versions/3.13.2.
Everything checks out.
And directly:
which dart # => /Users/you/.dvm/shims/dart
dvm which # => the SDK that resolves here, and which rule chose it
If which dart reports anything other than the shim, PATH order is the problem.
Other commands, with the pinned SDK#
The shim covers dart. For any other command that should run with the pinned SDK first on its
PATH — melos, a build script, your own tooling — use dvm exec:
dvm exec melos bootstrap
Every dart that melos goes on to spawn then resolves to the pinned SDK too, because
dvm exec puts that SDK's bin first on the child's PATH.