Skip to content

fix(link): linked versions override lockfile during resolution#8050

Merged
jdx merged 1 commit intomainfrom
fix/link-lockfile-override
Feb 7, 2026
Merged

fix(link): linked versions override lockfile during resolution#8050
jdx merged 1 commit intomainfrom
fix/link-lockfile-override

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 7, 2026

Summary

  • Linked tool versions (created by mise link) now always take priority over lockfile entries during version resolution
  • Previously, if a lockfile pinned a version (e.g., hk@1.35.0), it would override a linked version (e.g., hk@brew), causing the tool to be reported as missing
  • Added has_linked_version() check that detects user-created symlinks (absolute path targets) vs runtime symlinks (relative ./ targets)

Fixes #8049

Test plan

  • Added e2e test test_link_lockfile that creates a linked tool version alongside a lockfile and verifies the linked version is used without "missing" warnings
  • Existing test_link e2e test passes
  • Existing test_lock e2e tests pass
  • All 447 unit tests pass

🤖 Generated with Claude Code


Note

Medium Risk
Touches core tool version resolution logic and changes precedence rules between linked installs and lockfiles, which could affect installs/listing behavior across tools.

Overview
Linked tool versions created via mise link now override lockfile resolution: ToolVersion::resolve skips lockfile_resolve when a user-linked install exists (detected by scanning the installs dir for symlinks with absolute targets).

Adds an e2e regression test e2e/cli/test_link_lockfile that sets up a linked tiny@brew alongside a lockfile pin, then asserts mise ls shows the linked version and does not report the lockfile version as missing.

Written by Cursor Bugbot for commit fd2b4c6. This will update automatically on new commits. Configure here.

When a tool has a user-linked version (created by `mise link`), skip
lockfile resolution so the linked version takes priority. Previously,
the lockfile would pin a remote version (e.g., 1.35.0) that doesn't
exist locally, causing the linked tool to be reported as missing.

Fixes #8049

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 7, 2026 21:26
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical issue where mise link versions were being incorrectly superseded by lockfile entries, leading to tools being marked as missing. By ensuring that explicitly linked versions always take precedence during resolution, the change improves the reliability and predictability of tool management, particularly for users who rely on custom or system-wide linked tools.

Highlights

  • Linked Version Priority: Linked tool versions created via mise link now consistently override lockfile entries during version resolution, ensuring user-defined versions are always respected.
  • Missing Tool Resolution: Addresses a bug where lockfiles could previously override linked versions, causing tools to be incorrectly reported as missing.
  • Symlink Differentiation: Implemented has_linked_version() to distinguish between user-created absolute symlinks (linked versions) and runtime relative symlinks.
  • New E2E Test: A new end-to-end test, test_link_lockfile, has been added to validate the correct priority of linked versions over lockfiles.
Changelog
  • e2e/cli/test_link_lockfile
    • Added a new end-to-end test case to confirm that linked tool versions are correctly prioritized over lockfile-pinned versions, preventing "missing" warnings.
  • src/toolset/tool_version.rs
    • Modified the ToolVersion::resolve method to incorporate a check for linked versions, ensuring they bypass lockfile resolution.
    • Introduced the has_linked_version utility function, which identifies user-created symlinks by checking for absolute path targets, differentiating them from internal runtime symlinks.
Activity
  • A new end-to-end test, test_link_lockfile, was added to cover the new functionality.
  • Existing test_link and test_lock end-to-end tests continue to pass.
  • All 447 unit tests passed successfully.
  • The changes address and fix issue `mise link`ed tools reported missing if `mise.lock` exists #8049.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensures tools linked via mise link take precedence over lockfile-pinned versions during resolution, preventing linked tools from being incorrectly reported as missing.

Changes:

  • Skip lockfile resolution when a linked tool version exists.
  • Add has_linked_version() to detect user-linked (absolute-target) symlinks in the installs directory.
  • Add an e2e regression test covering linked-version vs lockfile precedence.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/toolset/tool_version.rs Adds linked-version detection and updates version resolution to prefer links over lockfile entries
e2e/cli/test_link_lockfile Adds an e2e regression test validating linked versions override lockfile pins without “missing” warnings

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +419 to +435
fn has_linked_version(ba: &BackendArg) -> bool {
let installs_dir = &ba.installs_path;
let Ok(entries) = std::fs::read_dir(installs_dir) else {
return false;
};
for entry in entries.flatten() {
let path = entry.path();
if let Ok(Some(target)) = crate::file::resolve_symlink(&path) {
// Runtime symlinks start with "./" (e.g., latest -> ./1.35.0)
// User-linked symlinks point to absolute paths (e.g., brew -> /opt/homebrew/opt/hk)
if target.is_absolute() {
return true;
}
}
}
false
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_linked_version() assumes crate::file::resolve_symlink(&path) returns the raw symlink target so that target.is_absolute() can distinguish user links from runtime ./... links. If resolve_symlink instead returns a resolved/canonicalized path (which the name strongly suggests), then runtime symlinks like latest -> ./1.35.0 will resolve to an absolute path and be misclassified as a user-linked version, changing resolution behavior broadly. Prefer using a helper that returns the direct read_link() value (unresolved), or rename/split helpers so this function explicitly uses the non-resolving variant.

Copilot uses AI. Check for mistakes.
Comment on lines 52 to 55
if opts.use_locked_version
&& !has_linked_version(request.ba())
&& let Some(lt) = request.lockfile_resolve(config)?
{
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_linked_version() performs a filesystem read_dir scan of the installs directory and is now on the hot path for every resolution when use_locked_version is enabled. Consider caching the result per backend/tool for the duration of the process (or at least per ResolveOptions/resolution run) to avoid repeated directory scans during commands that resolve many tools.

Copilot uses AI. Check for mistakes.

echo "=== Setup: install tiny and create a linked version ==="
rm -f mise.toml mise.lock
mise install tiny@3.1.0
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test installs tiny@3.1.0 but does not uninstall it during cleanup. If the e2e suite shares a persistent MISE data directory across tests, that leftover install can cause cross-test state leakage. Consider uninstalling tiny@3.1.0 in cleanup as well (or run the test under an isolated temp data dir if that’s the suite convention).

Copilot uses AI. Check for mistakes.

echo "=== Cleanup ==="
rm -rf mise.toml mise.lock tmp/tiny-brew
mise uninstall tiny@brew 2>/dev/null || true
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test installs tiny@3.1.0 but does not uninstall it during cleanup. If the e2e suite shares a persistent MISE data directory across tests, that leftover install can cause cross-test state leakage. Consider uninstalling tiny@3.1.0 in cleanup as well (or run the test under an isolated temp data dir if that’s the suite convention).

Suggested change
mise uninstall tiny@brew 2>/dev/null || true
mise uninstall tiny@brew 2>/dev/null || true
mise uninstall tiny@3.1.0 2>/dev/null || true

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly implements the desired behavior of prioritizing linked tool versions over lockfile entries. The logic added to ToolVersion::resolve and the new has_linked_version function are sound. The new e2e test provides good coverage for the fix. I have one suggestion to refactor has_linked_version to be more idiomatic, but overall this is a great change.

Comment on lines +424 to +434
for entry in entries.flatten() {
let path = entry.path();
if let Ok(Some(target)) = crate::file::resolve_symlink(&path) {
// Runtime symlinks start with "./" (e.g., latest -> ./1.35.0)
// User-linked symlinks point to absolute paths (e.g., brew -> /opt/homebrew/opt/hk)
if target.is_absolute() {
return true;
}
}
}
false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This for loop can be refactored into a more concise and idiomatic iterator-based approach using any(). This improves readability by expressing the intent of checking if any entry satisfies the condition directly. The inline comment can be removed as its content is already covered by the function's docstring.

    entries.flatten().any(|entry| {
        if let Ok(Some(target)) = crate::file::resolve_symlink(&entry.path()) {
            target.is_absolute()
        } else {
            false
        }
    })

@github-actions
Copy link

github-actions bot commented Feb 7, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.6 x -- echo 20.6 ± 0.4 19.9 22.1 1.00
mise x -- echo 21.7 ± 0.9 20.7 30.6 1.05 ± 0.05

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.6 env 20.4 ± 0.5 19.2 21.8 1.00
mise env 20.5 ± 0.7 19.6 29.9 1.01 ± 0.04

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.6 hook-env 20.7 ± 0.4 19.9 25.2 1.00
mise hook-env 21.6 ± 0.6 20.6 23.9 1.05 ± 0.04

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.6 ls 18.7 ± 0.3 18.1 20.1 1.00
mise ls 19.5 ± 0.5 18.6 21.4 1.05 ± 0.03

xtasks/test/perf

Command mise-2026.2.6 mise Variance
install (cached) 110ms 119ms -7%
ls (cached) 70ms 71ms -1%
bin-paths (cached) 74ms 75ms -1%
task-ls (cached) 533ms 539ms -1%

@jdx jdx merged commit f2e6508 into main Feb 7, 2026
37 checks passed
@jdx jdx deleted the fix/link-lockfile-override branch February 7, 2026 21:55
mise-en-dev added a commit that referenced this pull request Feb 8, 2026
### 🚀 Features

- **(shim)** add native .exe shim mode for Windows by @jdx in
[#8045](#8045)

### 🐛 Bug Fixes

- **(install)** preserve config options and registry defaults by @jdx in
[#8044](#8044)
- **(link)** linked versions override lockfile during resolution by @jdx
in [#8050](#8050)
- **(release)** preserve aqua-registry sections in changelog across
releases by @jdx in [#8047](#8047)
- ls --all-sources shows duplicate entries by @roele in
[#8042](#8042)

### 📚 Documentation

- replace "inherit" terminology with config layering by @jdx in
[#8046](#8046)

### 📦 Registry

- switch oxlint to npm backend by default by @risu729 in
[#8038](#8038)
- add orval (npm:orval) by @zdunecki in
[#8051](#8051)

### New Contributors

- @zdunecki made their first contribution in
[#8051](#8051)
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
)

## Summary
- Linked tool versions (created by `mise link`) now always take priority
over lockfile entries during version resolution
- Previously, if a lockfile pinned a version (e.g., `hk@1.35.0`), it
would override a linked version (e.g., `hk@brew`), causing the tool to
be reported as missing
- Added `has_linked_version()` check that detects user-created symlinks
(absolute path targets) vs runtime symlinks (relative `./` targets)

Fixes jdx#8049

## Test plan
- [x] Added e2e test `test_link_lockfile` that creates a linked tool
version alongside a lockfile and verifies the linked version is used
without "missing" warnings
- [x] Existing `test_link` e2e test passes
- [x] Existing `test_lock` e2e tests pass
- [x] All 447 unit tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches core tool version resolution logic and changes precedence
rules between linked installs and lockfiles, which could affect
installs/listing behavior across tools.
> 
> **Overview**
> Linked tool versions created via `mise link` now **override lockfile
resolution**: `ToolVersion::resolve` skips `lockfile_resolve` when a
user-linked install exists (detected by scanning the installs dir for
symlinks with absolute targets).
> 
> Adds an e2e regression test `e2e/cli/test_link_lockfile` that sets up
a linked `tiny@brew` alongside a lockfile pin, then asserts `mise ls`
shows the linked version and does not report the lockfile version as
missing.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fd2b4c6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
### 🚀 Features

- **(shim)** add native .exe shim mode for Windows by @jdx in
[jdx#8045](jdx#8045)

### 🐛 Bug Fixes

- **(install)** preserve config options and registry defaults by @jdx in
[jdx#8044](jdx#8044)
- **(link)** linked versions override lockfile during resolution by @jdx
in [jdx#8050](jdx#8050)
- **(release)** preserve aqua-registry sections in changelog across
releases by @jdx in [jdx#8047](jdx#8047)
- ls --all-sources shows duplicate entries by @roele in
[jdx#8042](jdx#8042)

### 📚 Documentation

- replace "inherit" terminology with config layering by @jdx in
[jdx#8046](jdx#8046)

### 📦 Registry

- switch oxlint to npm backend by default by @risu729 in
[jdx#8038](jdx#8038)
- add orval (npm:orval) by @zdunecki in
[jdx#8051](jdx#8051)

### New Contributors

- @zdunecki made their first contribution in
[jdx#8051](jdx#8051)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants