fix(env): use runtime symlink paths for fuzzy versions#9143
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a runtime_path method to prioritize runtime symlinks (such as latest) over specific versioned installation paths when resolving tool binaries. This change ensures that environment paths remain stable even when the underlying version is updated. The PR also updates the path caching logic to include the requested version in the cache key. Feedback was provided to use is_dir() instead of exists() when validating the runtime path to prevent potential issues on Windows where symlinks might be incorrectly handled as files.
| }; | ||
| let path = self.ba().installs_path.join(&pathname); | ||
| let path = env::find_in_shared_installs(path, &self.ba().tool_dir_name(), &pathname); | ||
| if path.exists() && is_runtime_symlink(&path) { |
There was a problem hiding this comment.
Using path.is_dir() is preferred over path.exists() here. On Windows, if mise is unable to create real symlinks (e.g., due to missing permissions), it may create a text file containing the target path instead. While is_runtime_symlink might return true for such a file, including a file path in PATH (e.g., .../latest/bin) will not work as expected for the shell. is_dir() ensures that the path is a valid directory (or a symlink to one) before it is used for environment-facing tool paths.
| if path.exists() && is_runtime_symlink(&path) { | |
| if path.is_dir() && is_runtime_symlink(&path) { |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 405615c. Configure here.
Greptile SummaryIntroduces Confidence Score: 5/5Safe to merge — all remaining findings from prior rounds are P2 scope/perf notes that the author has explicitly acknowledged, and no new P0/P1 issues were found. The core logic in No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[runtime_path called] --> B{tv.locked?}
B -- yes --> Z[return install_path]
B -- no --> C{runtime_pathname returns Some?}
C -- no --> Z
C -- yes --> D[path = installs_path / pathname]
D --> E[find_in_shared_installs]
E --> F{path.is_dir AND is_runtime_symlink?}
F -- yes --> G[return symlink path e.g. installs/python/3.14/bin]
F -- no --> H{Windows AND path.is_file AND is_runtime_symlink?}
H -- yes --> I{target.is_dir?}
I -- yes --> J[return absolutized target]
I -- no --> Z
H -- no --> Z
subgraph runtime_pathname
R1{ToolRequest type?}
R1 -- Version AND version != tv.version --> R2[return requested version string]
R1 -- Prefix --> R3[return prefix string]
R1 -- Other --> R4[return None]
end
Reviews (4): Last reviewed commit: "fix(env): use runtime symlink paths for ..." | Re-trigger Greptile |
6d759c4 to
a65c8aa
Compare
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 x -- echo |
21.9 ± 0.5 | 20.9 | 24.0 | 1.00 |
mise x -- echo |
22.2 ± 0.4 | 21.4 | 23.5 | 1.01 ± 0.03 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 env |
21.2 ± 0.7 | 20.5 | 28.0 | 1.00 |
mise env |
21.9 ± 0.4 | 21.0 | 23.6 | 1.03 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 hook-env |
21.7 ± 0.4 | 21.0 | 26.6 | 1.00 |
mise hook-env |
22.2 ± 0.3 | 21.6 | 25.5 | 1.02 ± 0.03 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.15 ls |
19.1 ± 0.3 | 18.5 | 21.2 | 1.00 |
mise ls |
19.7 ± 0.3 | 19.1 | 21.0 | 1.03 ± 0.02 |
xtasks/test/perf
| Command | mise-2026.4.15 | mise | Variance |
|---|---|---|---|
| install (cached) | 145ms | 146ms | +0% |
| ls (cached) | 76ms | 76ms | +0% |
| bin-paths (cached) | 80ms | 80ms | +0% |
| task-ls (cached) | 818ms | 783ms | +4% |
a65c8aa to
e4127b2
Compare
### 🚀 Features - **(registry)** add .perl-version support for perl by @ergofriend in [#9102](#9102) - **(task)** add Tera template support for inline table run tasks by @iamkroot in [#9079](#9079) ### 🐛 Bug Fixes - **(env)** use runtime symlink paths for fuzzy versions by @jdx in [#9143](#9143) - **(github)** use full token resolution chain for attestation verification by @jdx in [#9154](#9154) - **(go)** Remove install-time version override for subpath packages by @c22 in [#9135](#9135) - **(npm)** respect install_before when resolving dist-tag versions by @webkaz in [#9145](#9145) - **(self-update)** ensure subcommand exists by @salim-b in [#9144](#9144) - **(task)** show available tasks when run target missing by @jdx in [#9141](#9141) - **(task)** forward task help args and add raw_args by @jdx in [#9118](#9118) - **(task)** remove red/yellow from task prefix colors by @lechuckcaptain in [#8782](#8782) - **(task)** merge TOML task block into same-named file task and surface resolved dir by @jdx in [#9147](#9147) - **(toolset)** round-trip serialized tool options by @atharvasingh7007 in [#9124](#9124) - **(vfox)** fallback to absolute bin path if env_keys not set by @80avin in [#9151](#9151) ### 📚 Documentation - make agent guide wording generic by @jdx in [#9142](#9142) ### 📦️ Dependency Updates - update ghcr.io/jdx/mise:deb docker digest to e019cb9 by @renovate[bot] in [#9160](#9160) - update ghcr.io/jdx/mise:copr docker digest to 8d25608 by @renovate[bot] in [#9159](#9159) - update ghcr.io/jdx/mise:rpm docker digest to 22e52da by @renovate[bot] in [#9161](#9161) - update ghcr.io/jdx/mise:alpine docker digest to a3da97c by @renovate[bot] in [#9158](#9158) - update rust docker digest to 4a2ef38 by @renovate[bot] in [#9162](#9162) - update ubuntu:24.04 docker digest to c4a8d55 by @renovate[bot] in [#9164](#9164) - update rust crate aws-lc-rs to v1.16.3 by @renovate[bot] in [#9165](#9165) - update ubuntu docker tag to resolute-20260413 by @renovate[bot] in [#9169](#9169) - update rust crate clap to v4.6.1 by @renovate[bot] in [#9166](#9166) - update taiki-e/install-action digest to a2352fc by @renovate[bot] in [#9163](#9163) - update rust crate ctor to 0.10 by @renovate[bot] in [#9170](#9170) - update rust crate tokio to v1.52.1 by @renovate[bot] in [#9167](#9167) - update rust crate rmcp-macros to 0.17 by @renovate[bot] in [#9173](#9173) - update rust crate signal-hook to 0.4 by @renovate[bot] in [#9177](#9177) - update rust crate zipsign-api to 0.2 by @renovate[bot] in [#9180](#9180) - update rust crate toml_edit to 0.25 by @renovate[bot] in [#9179](#9179) - update rust crate strum to 0.28 by @renovate[bot] in [#9178](#9178) ### 📦 Registry - add ibmcloud by @dnwe in [#9139](#9139) - add rush by @jdx in [#9146](#9146) ### New Contributors - @80avin made their first contribution in [#9151](#9151) - @atharvasingh7007 made their first contribution in [#9124](#9124) - @lechuckcaptain made their first contribution in [#8782](#8782) - @ergofriend made their first contribution in [#9102](#9102) - @dnwe made their first contribution in [#9139](#9139) ## 📦 Aqua Registry Updates #### New Packages (3) - [`controlplaneio-fluxcd/flux-operator`](https://github.com/controlplaneio-fluxcd/flux-operator) - [`dependency-check/DependencyCheck`](https://github.com/dependency-check/DependencyCheck) - [`kiro.dev/kiro-cli`](https://github.com/kiro.dev/kiro-cli) #### Updated Packages (2) - [`jreleaser/jreleaser/standalone`](https://github.com/jreleaser/jreleaser/standalone) - [`sigstore/cosign`](https://github.com/sigstore/cosign)

Summary
Use runtime symlink paths when building environment-facing tool paths for fuzzy or rolling requests, while keeping concrete install paths for install state and filesystem operations.
This fixes a case where
python = "3.14"resolved to3.14.4and PATH used.../installs/python/3.14.4/bininstead of the stable requested-version symlink.../installs/python/3.14/bin. Tools that cache interpreter paths, such as virtualenvs, can then survive patch upgrades where the concrete install directory changes.Root Cause
ToolVersion::install_path()is intentionally based on the resolved version and is used broadly for install directories, downloads, cache paths, incomplete markers, and uninstall state. PATH generation also used that same concrete install path through backendlist_bin_paths(), so runtime symlinks created byruntime_symlinks::rebuild()were not used for active PATH entries.Changes
ToolVersion::runtime_path()for PATH-facing paths.runtime_path()in the default backend bin path implementation and asdf bin paths.runtime_path()only returns traversable directories, and resolve Windows file-based pseudo-symlinks to their concrete install directory.dummy@1.0anddummy@prefix:1returning runtime symlink bin paths.latestbin-path expectations to use the existinglatestruntime symlink.Validation
cargo test runtime_path_does_not_return_file_based_runtime_symlinkmise run formatmise run test:e2e e2e/lockfile/test_lockfile_execmise run test:e2e e2e/tools/test_runtime_symlinks e2e/cli/test_bin_paths e2e/tools/test_path_ordermarkdownlint README.md docs/demo.md docs/dev-tools/index.md docs/dev-tools/mise-lock.md docs/dev-tools/shims.md docs/environments/index.md docs/getting-started.md docs/lang/node.md docs/lang/python.md docs/mise-cookbook/docker.md docs/mise-cookbook/nodejs.md docs/tips-and-tricks.md docs/troubleshooting.md docs/walkthrough.mdhklint set, includingcargo check --all-featuresThis PR was generated by Codex.
Note
Medium Risk
Changes how PATH/bin-paths are computed for non-pinned tool requests, which can affect runtime selection and downstream tooling that relies on specific filesystem paths. While scoped and covered by new tests, it touches core environment construction across backends.
Overview
Ensures environment-facing tool paths (e.g.,
PATH/mise bin-paths) use the requested-version runtime symlink (like.../installs/python/3.15/binor.../installs/node/lts/bin) instead of the resolved patch install directory, by introducingToolVersion::runtime_path()and routing backendlist_bin_paths()through it (including the asdf backend).Updates toolset path caching to key on
requested=resolvedto avoid stale path reuse when different requests resolve to the same concrete version, adds/adjusts unit + e2e coverage for runtime symlink bin paths (includinglatest, prefix, and version requests), and refreshes docs/demo examples to Node 26 / Python 3.15 while documenting the runtime-symlink PATH behavior.Reviewed by Cursor Bugbot for commit a65c8aa. Bugbot is set up for automated code reviews on this repo. Configure here.