Skip to content

fix(shim): make shim-map cache version-aware#257

Merged
CalvinAllen merged 1 commit intomainfrom
fix/shim/version-aware-shim-map
May 8, 2026
Merged

fix(shim): make shim-map cache version-aware#257
CalvinAllen merged 1 commit intomainfrom
fix/shim/version-aware-shim-map

Conversation

@CalvinAllen
Copy link
Copy Markdown
Contributor

Summary

  • 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, a shim like uv (installed via pip install uv against only Python 3.9.9) would silently route to whatever Python version was active — even one that didn't have uv at 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.
  • 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.

Resolves #253

Behavior change (verified locally)

Setup: Python 3.8.9, 3.9.9, 3.12.12 installed; uv only present in 3.9.9's Scripts/; current dir locally pins python to 3.8.9.

Before this PR (with #254 merged):

$ uv --version
✗ 'uv' is not available in Python 3.8.9
→ This shim exists because another installed Python version provides it.
→ Install 'uv' for the active version, or switch to a version that has it.

After:

$ uv --version
✗ 'uv' is not available in Python 3.8.9
→ Available in: Python 3.9.9
→ Switch with: dtvem global python 3.9.9

Same improvement for dtvem which uv.

Schema migration

The cache file at ~/.dtvem/cache/shim-map.json changes shape:

-{
-  "uv": "python"
-}
+{
+  "uv": {
+    "runtime": "python",
+    "versions": ["3.9.9"]
+  }
+}

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 runs dtvem reshim, which writes the new schema). No user-visible migration is required; the next reshim (manual or post-install-triggered) regenerates with version data.

Notable correctness fix

RehashWithCallback no 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 only python.exe and no pip.exe, but reshim was registering pip anyway. The post-install state of the shim-map now matches what's actually on disk, scanned via the existing bin / Scripts / root directory walk.

Test plan

  • ./rnr check (format, lint, full test suite) — clean across linux/darwin/windows
  • New unit tests in internal/shim/cache_test.go: legacy-schema fallback, version unioning across multiple MergeShimMap calls, runtime-name overwrite, in-memory cache invalidation, omitempty Versions on save, full-entry Lookup
  • End-to-end on Windows with the user's actual install (uv only in 3.9.9):
    • uv --version from a 3.8.9-pinned dir → rich error with "Available in: Python 3.9.9"
    • uv --version from a 3.9.9-pinned dir → runs uv 0.9.22 correctly
    • dtvem which uv from 3.8.9 → same rich error
    • dtvem which uv from 3.9.9 → resolves to Scripts/uv.exe
    • dtvem which python (primary shim, present everywhere) → unaffected
  • CI passes on Windows / macOS / Linux

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.
@CalvinAllen CalvinAllen merged commit 56005c4 into main May 8, 2026
11 checks passed
@CalvinAllen CalvinAllen deleted the fix/shim/version-aware-shim-map branch May 8, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: shim map is not version-aware - cross-version contamination on reshim

1 participant