Repository navigation
fix(npm): resolve unlocked latest to the npm version of a graph-locked install - #14307
Conversation
…d install When the newest installed copy of an npm tool was one a lockfile built (`<version>~aube~<digest>`), another config asking for `latest` without a lockfile took that directory name as its version. In the identity install layout it then hashed to an installation that does not exist, so the tool was reported missing and its shims failed. Strip the private suffix when `latest` resolves to an installed version, and keep pointing at the installation the name links to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4NPKK6MGCCQHTNN4CG27AHT
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to The change makes an unlocked latest request for an npm tool installed from a lockfile report the real npm version instead of the private install directory suffix. No actionable merge-blocking risk remains. Pre-merge checks |
|
|
#14307 fixed unlocked `latest` on main by stripping the `~aube~` suffix and keeping the graph-locked install it found. Take that design, drop this branch's competing one, and apply the same normalization where a prefix, an installed match, or a package.json range picks from installed versions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4NRY8PFG55JYFZ092FK7QSN
Instruction countsNothing was compared, and so nothing was gated. No series appears on both sides: either the base has no measurements recorded, or the two were measured on different runner classes, which are deliberately not comparable — counts shift between machine types by more than a real regression does. New, nothing to compare against: Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.
|
… layout (#14309) <!-- entire-trail-link-start --> https://entire.io/gh/jdx/mise/trails/651 <!-- entire-trail-link-end --> This follows up on #14307, which fixed the npm tool reported missing in discussion #14276. A project with a `mise.lock` installs the tool from its locked dependency graph, which is stored as `<version>~aube~<digest>`. Another config with no lockfile then resolves `latest` against installed versions. #14307 made that config report the npm version, but with `install_layout = "identity"` it also made the config run the project's lockfile-built installation: ``` $ mise -C global current npm:inflight 1.0.6 $ mise -C global where npm:inflight ~/.local/share/mise/installs/inflight-p54rj3gd # the project's graph install ``` The identity layout keeps a graph install to the lockfile that made it. It already did that for a pinned version: `npm:inflight@1.0.6` in the global config never used the project's install, but `npm:inflight@latest` did. Now an unlocked request stands for the npm version and gets its own plain installation. If that plain install is missing, mise reports it as missing and `mise install` adds it, separate from the project's. The project keeps running its graph install, and later lockfile changes in the project no longer change what the global config runs. The legacy layout is unchanged from #14307: it reports the npm version and keeps using the directory that was found. Requests by version prefix (`"npm:inflight" = "1.0"`), an installed fuzzy match, and package.json semver ranges also resolve against installed versions. Before this change they still took the private name as the version: ``` $ mise -C global current npm:inflight # "npm:inflight" = "1.0" 1.0.6~aube~883b3ea0f0f63119 ``` They now get the same handling as `latest`. pipx tools installed from a uv graph (`~uv~`) are treated the same way. Note that `mise where npm:inflight` with no version re-resolves the configured request without reading `mise.lock`. Inside a locked project under the identity layout, it can therefore print the plain install while `mise ls --current` and the shims use the graph install. This was already true for pinned versions and is not changed here. ### Validation - `backend/test_npm_aube_unlocked_latest` now runs `latest` and a `1.0` prefix in an unlocked config under both layouts, next to a project locked with `mise lock`. Identity layout: the version is reported without `~aube~`, the unlocked config is missing until `mise install` adds a separate plain install, the prefix request uses that plain install, and the project still runs its graph install. Legacy layout: both requests use the found directory. The test fails on `main`. With only the fuzzy-match path left unconverted, the prefix case fails with `1.0.6~aube~883b3ea0f0f63119`. - `test_npm_aube*` and `cli/test_install_layout*` e2e tests, the `tool_version`/`lockfile`/`install_layout`/`npm` unit tests, and `cargo clippy --workspace --all-features --all-targets -- -D warnings` pass. One `tool_version` unit test, `has_linked_version_ignores_mise_managed_absolute_targets`, fails in this sandbox with and without this change. - The package.json range path is converted the same way but has no e2e coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) *AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5-5; version: 2.1.295.* <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core version resolution for npm aube and pipx uv graph installs across multiple code paths; wrong behavior could mis-route installs or PATH, though legacy layout is preserved and e2e coverage was expanded. > > **Overview** > Fixes unlocked npm/pipx resolution when the only on-disk match is a lockfile graph directory (`<version>~aube~<digest>` or `~uv~`). **Identity layout** now treats `latest`, version prefixes, fuzzy matches, and package.json semver ranges as the **logical npm version** and resolves them to a **separate plain install**, not the locked project’s graph tree—so they show as missing until `mise install` and the lockfile project keeps its graph install. **Legacy layout** behavior is unchanged: report the logical version but keep using the directory that was found. > > Implementation refactors install-path stripping into `install_path_identity_version()` and routes all “pick from installed versions” paths through new **`from_installed()`**, which either re-resolves under identity layout or calls `strip_install_path_identity()` as before. > > The **`test_npm_aube_unlocked_latest`** e2e test now locks via `mise lock`, adds a **`1.0` prefix** config, and asserts identity vs legacy `where` paths and that the locked project still runs its graph install. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0655288. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Unlocked, `latest`, and version-prefix requests now resolve consistently with identity-based and legacy installation layouts. * Locked projects continue using their graph-specific installations, while identity-based layouts keep unlocked installs separate from locked installs. * Tool listings no longer expose internal graph identifiers for unlocked requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
<!-- entire-trail-link-start --> https://entire.io/gh/jdx/mise/trails/640 <!-- entire-trail-link-end --> ### 🚀 Features - **(bootstrap)** add apt repositories and PPAs with [bootstrap.apt.sources] by @jdx in [#14306](#14306) - **(bootstrap)** embed apt source signing keys inline in .sources files by @jdx in [#14348](#14348) - **(bootstrap)** render templates in [bootstrap.repos] checkout paths by @jdx in [#14359](#14359) - **(bootstrap)** declare macOS login items by @jdx in [#14354](#14354) - **(bootstrap)** layer dnf packages with rpm-ostree on Fedora Atomic hosts by @jdx in [#14357](#14357) - **(bootstrap)** support run0 and doas for privilege elevation by @jdx in [#14351](#14351) - **(dotfiles)** show tracked entries that no variant selects in status by @jdx in [#14266](#14266) - **(dotfiles)** group mise dot status into sections by @jdx in [#14361](#14361) - **(github)** add oauth_copy_code to copy the device code to the clipboard by @jdx in [#14269](#14269) - **(github)** keep the native OAuth refresh token in a password manager or keychain by @jdx in [#14360](#14360) - **(http)** add libc() and avx2() template functions by @jdx in [#14298](#14298) - **(pypi)** read upload times from PEP 691 simple indexes by @jdx in [#14352](#14352) - **(rust)** download rustup-init from RUSTUP_UPDATE_ROOT by @jdx in [#14356](#14356) ### 🐛 Bug Fixes - **(aqua)** warn once for packages with no repo to list versions from by @jdx in [#14336](#14336) - **(backend)** keep decoded download file names inside the download directory by @JamBalaya56562 in [#14274](#14274) - **(bootstrap)** support dnf package groups by @jdx in [#14347](#14347) - **(bootstrap)** don't print a stdin-only helper as a manual command by @jdx in [#14350](#14350) - **(bootstrap)** explain why dnf packages cannot install on ostree hosts by @jdx in [#14353](#14353) - **(brew)** install bottle etc files and run post_install_steps when pouring by @jdx in [#14272](#14272) - **(brew)** refuse to chown a prefix owned by another user by @jdx in [#14345](#14345) - **(brew)** preserve protected post-install copy destinations by @jdx in [#14311](#14311) - **(brew-cask)** support generate completions in evaluated casks by @pmmmwh in [#14255](#14255) - **(docker)** install libatomic1 in the Debian image so Node.js 26 runs by @jdx in [#14303](#14303) - **(dotfiles)** keep existing ciphertext when an encrypted file is unchanged by @jdx in [#14300](#14300) - **(dotfiles)** stop a newly selected profile from deleting its shared file by @jdx in [#14275](#14275) - **(dotfiles)** restore config entries when adopting under a symlinked home by @jdx in [#14315](#14315) - **(dotfiles)** show per-file diffs for directory copy entries in dot diff by @jdx in [#14333](#14333) - **(dotfiles)** apply mise's own config before planning the other entries by @jdx in [#14339](#14339) - **(go)** read an exact go line in go.mod as the toolchain version by @jdx in [#14308](#14308) - **(http)** retry artifact downloads started by mise exec and shims by @jdx in [#14342](#14342) - **(npm)** resolve unlocked latest to the npm version of a graph-locked install by @jdx in [#14307](#14307) - **(npm)** give unlocked requests their own install under the identity layout by @jdx in [#14309](#14309) - **(oci)** ignore host apt hooks when installing apt bootstrap packages by @jdx in [#14296](#14296) - **(packslip)** let a workflow list carry the signer pin across a workflow move by @jdx in [#14338](#14338) - **(packslip)** keep a renamed project's lockfile signer under packslip 1.7 by @jdx in [#14362](#14362) - **(prune)** keep a uv-locked pipx tool's installs for every Python by @jdx in [#14335](#14335) - **(prune)** keep versions of task-scoped tools by @jdx in [#14334](#14334) - **(schema)** accept [bootstrap.secrets] by @JamBalaya56562 in [#14322](#14322) - **(schema)** accept [bootstrap.groups] and [bootstrap.users] by @JamBalaya56562 in [#14340](#14340) - **(schema)** accept table forms for vars._.file and vars._.source by @JamBalaya56562 in [#14383](#14383) - **(shim)** keep other projects' command wrappers on reshim by @jdx in [#14355](#14355) - **(task)** bump gix so remote tasks work in git hooks by @any-victor in [#14316](#14316) - hide the Windows console for the tool-update service by @jdx in [#14344](#14344) ### 🚜 Refactor - replace serde_yaml with serde-saphyr by @jdx in [#14328](#14328) ### 📚 Documentation - **(bootstrap)** explain when to use --from versus --adopt by @jdx in [#14301](#14301) - **(bootstrap)** recommend fish = "auto" for fish shell activation by @jdx in [#14304](#14304) - **(dotfiles)** explain when to use sync, pull, and apply by @jdx in [#14343](#14343) - **(shims)** explain how to check whether a tool is installed by @jdx in [#14337](#14337) - switch body text to DM Sans and tighten the releases page by @jdx in [#14302](#14302) - self-host DM Sans and JetBrains Mono so they load on mise.jdx.dev by @jdx in [#14310](#14310) ### 🧪 Testing - **(bootstrap)** don't depend on bc being absent in the apt sources test by @jdx in [#14371](#14371) ### 📦️ Dependency Updates - install Homebrew uses_from_macos dependencies on Linux by @itskyf in [#14288](#14288) - bump packslip to 1.7.0 by @jdx in [#14363](#14363) - lock file maintenance by @renovate[bot] in [#14320](#14320) - update github actions by @renovate[bot] in [#14380](#14380) - update dependency python to v3.14.8 by @renovate[bot] in [#14381](#14381) - update rust crates by @renovate[bot] in [#14382](#14382) - update docker images by @renovate[bot] in [#14379](#14379) ### 📦 Registry - install opencode 2 from npm by @jdx in [#14295](#14295) ### Chore - **(ci)** switch to jactionlint v2 and drop zizmor by @jdx in [#14256](#14256) - fix CI for the aqua registry update and the unsafe-libyaml advisory by @jdx in [#14326](#14326) ### Ci - double the unit-macos job timeout by @jdx in [#14346](#14346) ### Security - **(config)** require trust for path: tool versions by @jdx in [#14358](#14358) ### New Contributors - @pmmmwh made their first contribution in [#14255](#14255) - @itskyf made their first contribution in [#14288](#14288) ## 📦 Aqua Registry Updates ### New Packages (3) - [`Alice39s/xmtr`](https://github.com/Alice39s/xmtr) - [`bufbuild/protoschema-plugins/protoc-gen-jsonschema`](https://github.com/bufbuild/protoschema-plugins) - [`takumin/qemu-bin`](https://github.com/takumin/qemu-bin) ### Updated Packages (3) - [`bootandy/dust`](https://github.com/bootandy/dust) - [`connectrpc/connect-go/protoc-gen-connect-go`](https://github.com/connectrpc/connect-go) - [`sharkdp/hyperfine`](https://github.com/sharkdp/hyperfine)
https://entire.io/gh/jdx/mise/trails/649
Discussion #14276 reports this bug. Suppose a project with a lockfile installs an npm tool. mise builds that install from the locked dependency graph and stores it as
<version>~aube~<digest>. Another config that asks for the same tool aslatestwithout a lockfile (typically the global config) resolveslatestto the newest installed version. It used that private directory name as the version:With
install_layout = "identity", the suffixed name was hashed into an installation identity that nothing has. The tool showed as missing and its shims failed even though it was installed. With the legacy layout the tool still ran, but the private name leaked intomise current, env and warnings as if it were the version.Now, when
latestresolves to an installed version, mise uses the npm version (3.9.9) and keeps pointing at the installation it found. This is the same normalizationmise lockgot in #13542. In the identity layout the found name is a compatibility link, so the installation is looked up through that link rather than by re-hashing the name. Pinningprettier@3.9.9in the global config was the workaround and is no longer needed.Validation
backend/test_npm_aube_unlocked_latest: installsnpm:inflight@latestfrom a lockfile in one directory, then resolveslatestwithout a lockfile in another. It checksmise current,ls --missing,lsandenvshow no~aube~name or missing tool, andwherepoints at the graph install. It runs under both layouts. Before the fix it failed in both (identity layout:1.0.6 (missing) [inflight-jfo7ej4u]). With the compatibility-link lookup removed, the identity-layoutwherecheck fails.test_npm_aube*,cli/test_install_layout*, and thetool_version/lockfile/install_layout/npmunit tests pass. Onetool_versionunit test,has_linked_version_ignores_mise_managed_absolute_targets, fails in this sandbox with and without this change.cargo clippy --workspace --all-features --all-targets -- -D warningspasses.🤖 Generated with Claude Code
AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5-5; version: 2.1.295.
Note
Medium Risk
Touches core version resolution and install-path lookup for npm
latestand aube-suffixed installs; behavior change is targeted but affects tool selection and shims globally when those installs exist.Overview
Fixes unlocked
latestfor npm tools when the newest on-disk install is a lockfile graph directory named\<version\>~aube~\<digest\>. mise now strips that private suffix to the real npm version and keeps the resolved install path, instead of treating the directory name as the version (missing tools, broken shims under identity layout,~aube~leaking intocurrent/ls/ warnings elsewhere).strip_install_path_identityresolves the install directory viaphysical_dirin the identity layout so a compatibility link name is not re-hashed into a nonexistent identity.resolve_versionapplies the same normalization whenlatestpickslatest_installed_version.Adds e2e
test_npm_aube_unlocked_latest(locked vs unlocked configs, both install layouts).Reviewed by Cursor Bugbot for commit aa615ea. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
latest. The detected version and resolved install path now match the installed tool, and commands no longer incorrectly report it as missing.