fix(shim): make shim-map cache version-aware#257
Merged
CalvinAllen merged 1 commit intomainfrom May 8, 2026
Merged
Conversation
The shim-map cache previously recorded shim → runtime only, with no
information about which installed runtime versions actually provide the
executable. Combined with the fact that `dtvem reshim` walks every
installed version and registers every executable it finds, this meant a
shim like `uv` (only installed against Python 3.9.9 via `pip install uv`)
would silently route to whatever Python version was active — even one
that didn't have `uv` at all.
This change:
- Bumps the shim-map schema to record `{runtime, versions[]}` per shim.
The loader tolerates the legacy flat-string schema by converting old
entries with empty Versions, signaling "version coverage unknown" so
callers skip the version check until the cache is regenerated.
- Updates `RehashWithCallback` to populate per-version coverage by
scanning each version's bin / Scripts / root directories. Drops the
blanket pre-population of the provider's declared core shims, which
previously asserted (incorrectly) that every installed version
provided every core shim — for example claiming a Windows embeddable
Python install provides `pip` even when its Scripts directory is
empty.
- Threads the install version through `CreateShimsForRuntime` so the
python / node / ruby providers register newly-installed shims with
the correct version.
- Adds a version-coverage check in `cmd/shim/main.go` and `cmd/which.go`:
when the active runtime version is not in the providing-versions list
recorded for a secondary shim, surface a richer error that names the
versions that DO provide it and how to switch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dtvem reshimwalks every installed version and registers every executable it finds, a shim likeuv(installed viapip install uvagainst only Python 3.9.9) would silently route to whatever Python version was active — even one that didn't haveuvat all. With [Bug]: shim silently runs runtime binary when secondary executable cannot be found #254 merged, the silent runtime-fallback was already replaced with a generic error; this PR turns that into a version-aware error that tells the user which installed versions actually provide the command and how to switch.{runtime, versions[]}per shim. The loader tolerates the legacy flat-string schema by converting old entries with emptyVersions, signaling "version coverage unknown" so callers skip the version check until the cache is regenerated.RehashWithCallbackto populate per-version coverage by scanning each version'sbin / Scripts / rootdirectories. Drops the blanket pre-population of the provider's declared core shims, which previously asserted (incorrectly) that every installed version provided every core shim — for example claiming a Windows embeddable Python install providespipeven when itsScripts/directory is empty.CreateShimsForRuntimeso the python / node / ruby providers register newly-installed shims with the correct version.cmd/shim/main.goandcmd/which.go: when the active runtime version is not in the providing-versions list recorded for a secondary shim, surface a richer error that names the versions that DO provide it and how to switch.Resolves #253
Behavior change (verified locally)
Setup: Python 3.8.9, 3.9.9, 3.12.12 installed;
uvonly present in 3.9.9'sScripts/; current dir locally pins python to 3.8.9.Before this PR (with #254 merged):
After:
Same improvement for
dtvem which uv.Schema migration
The cache file at
~/.dtvem/cache/shim-map.jsonchanges shape:Old-format files are auto-converted on read (entries get empty
Versions, and the version-coverage check is skipped — preserving the previous behavior — until the user runsdtvem reshim, which writes the new schema). No user-visible migration is required; the nextreshim(manual or post-install-triggered) regenerates with version data.Notable correctness fix
RehashWithCallbackno longer pre-populates the provider's declared core shims for every installed version. The previous code asserted that every install of a given runtime provides every core shim, which is false in practice — a Windows embeddable Python install often has onlypython.exeand nopip.exe, but reshim was registeringpipanyway. The post-install state of the shim-map now matches what's actually on disk, scanned via the existingbin / Scripts / rootdirectory walk.Test plan
./rnr check(format, lint, full test suite) — clean across linux/darwin/windowsinternal/shim/cache_test.go: legacy-schema fallback, version unioning across multipleMergeShimMapcalls, runtime-name overwrite, in-memory cache invalidation, omitempty Versions on save, full-entryLookupuvonly in 3.9.9):uv --versionfrom a 3.8.9-pinned dir → rich error with "Available in: Python 3.9.9"uv --versionfrom a 3.9.9-pinned dir → runsuv 0.9.22correctlydtvem which uvfrom 3.8.9 → same rich errordtvem which uvfrom 3.9.9 → resolves toScripts/uv.exedtvem which python(primary shim, present everywhere) → unaffected