Skip to content

fix(upgrade): respect tracked configs when pruning old versions#7926

Merged
jdx merged 3 commits intomainfrom
fix/upgrade-respect-tracked-configs
Jan 31, 2026
Merged

fix(upgrade): respect tracked configs when pruning old versions#7926
jdx merged 3 commits intomainfrom
fix/upgrade-respect-tracked-configs

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Jan 31, 2026

Summary

  • Make mise upgrade check tracked config files before uninstalling old versions
  • Consistent with how mise prune behaves

Problem

When running mise upgrade from one directory, it would uninstall old versions of tools without checking if those versions are still needed by projects in other directories (tracked configs).

For example:

  1. Global config has tiny = "latest"
  2. Project A has tiny = "3.0.0" (tracked)
  3. Running mise upgrade tiny from home would upgrade to 3.1.0 and uninstall 3.0.0
  4. Project A can no longer use its pinned version

Solution

Added a helper function get_versions_needed_by_tracked_configs() that:

  1. Gets all tracked config files
  2. Resolves each toolset to get concrete versions
  3. Returns a set of (tool_name, version_pathname) pairs

Before uninstalling an old version, we now check if it's in this set and skip uninstalling if so.

Fixes #7640

Test plan

  • Code compiles and passes linting
  • Manual testing with tracked configs

🤖 Generated with Claude Code


Note

Medium Risk
Changes uninstallation behavior in mise upgrade and centralizes version-resolution logic across tracked configs; mistakes could leave unused versions installed or, worse, still remove a needed version if the tracked-config resolution misses cases.

Overview
mise upgrade now checks all tracked config files before uninstalling an old tool version, and skips uninstall when that version is still needed by another tracked project.

This behavior is shared with mise prune via a new helper, get_versions_needed_by_tracked_configs(), which resolves each tracked config’s toolset and returns the concrete versions to keep (identified by (tool_short_name, tv_pathname)).

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

Copilot AI review requested due to automatic review settings January 31, 2026 13:55
@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 enhances the mise upgrade command to prevent the accidental uninstallation of tool versions that are still actively used by projects defined in tracked configuration files. By aligning its behavior with mise prune, the change improves the robustness and predictability of version management, ensuring that developers' pinned project dependencies remain stable even after global upgrades.

Highlights

  • Prevent unintended uninstallation: The mise upgrade command now checks all tracked configuration files before uninstalling old tool versions, ensuring that versions still required by other projects are preserved.
  • Behavior consistency: This change makes the behavior of mise upgrade consistent with mise prune, which already respects tracked configurations.
  • New helper function: A new asynchronous helper function, get_versions_needed_by_tracked_configs, was added to efficiently identify all tool versions currently in use across tracked configurations.
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

This PR fixes mise upgrade to respect tool versions needed by tracked config files before uninstalling old versions, making its behavior consistent with mise prune. Previously, upgrading a tool in one directory could uninstall versions still required by other projects.

Changes:

  • Added get_versions_needed_by_tracked_configs() helper to collect versions from tracked configs
  • Modified version removal logic to skip uninstalling versions still needed by tracked configs
  • Added debug logging when preserving versions needed by tracked configs

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

Comment on lines +308 to +311
async fn get_versions_needed_by_tracked_configs(
&self,
config: &Arc<Config>,
) -> std::collections::HashSet<(String, String)> {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The return type uses the fully qualified std::collections::HashSet instead of importing HashSet at the module level. For consistency with the rest of the codebase and improved readability, consider adding use std::collections::HashSet; to the imports and using HashSet<(String, String)> here.

Copilot uses AI. Check for mistakes.
Comment on lines +317 to +319
let mut ts = Toolset::from(trs);
if ts.resolve(config).await.is_ok() {
for (_, tv) in ts.list_current_versions() {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The variable name 'ts' is ambiguous. Consider using a more descriptive name like 'toolset' to improve code clarity.

Suggested change
let mut ts = Toolset::from(trs);
if ts.resolve(config).await.is_ok() {
for (_, tv) in ts.list_current_versions() {
let mut toolset = Toolset::from(trs);
if toolset.resolve(config).await.is_ok() {
for (_, tv) in toolset.list_current_versions() {

Copilot uses AI. Check for mistakes.
Comment on lines +315 to +316
for cf in tracked_configs.values() {
if let Ok(trs) = cf.to_tool_request_set() {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The variable name 'cf' is ambiguous. Consider using a more descriptive name like 'config_file' to improve code readability.

Suggested change
for cf in tracked_configs.values() {
if let Ok(trs) = cf.to_tool_request_set() {
for config_file in tracked_configs.values() {
if let Ok(trs) = config_file.to_tool_request_set() {

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 addresses an issue where mise upgrade could uninstall tool versions that are still in use by other projects. The change introduces a check against tracked configuration files before uninstalling old versions, which aligns its behavior with mise prune. The implementation is sound and correctly solves the problem. My main feedback is to suggest a refactoring to centralize the new logic for determining needed versions, which is currently duplicated from the prune command. This will improve code maintainability.

Comment on lines +306 to +331
/// Get all tool versions that are needed by tracked config files.
/// This ensures we don't uninstall versions that other projects still need.
async fn get_versions_needed_by_tracked_configs(
&self,
config: &Arc<Config>,
) -> std::collections::HashSet<(String, String)> {
let mut needed = std::collections::HashSet::new();
match config.get_tracked_config_files().await {
Ok(tracked_configs) => {
for cf in tracked_configs.values() {
if let Ok(trs) = cf.to_tool_request_set() {
let mut ts = Toolset::from(trs);
if ts.resolve(config).await.is_ok() {
for (_, tv) in ts.list_current_versions() {
needed.insert((tv.ba().short.to_string(), tv.tv_pathname()));
}
}
}
}
}
Err(e) => {
debug!("Failed to get tracked config files: {}", e);
}
}
needed
}
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 function is very useful and correctly implements the desired logic. However, its logic is almost identical to what's currently in src/cli/prune.rs for determining prunable tools. To avoid code duplication and improve maintainability, this logic should be extracted to a more central location.

I suggest moving this function to src/config/mod.rs as a free function that returns a Result. This would allow both upgrade and prune commands to use it, with each command handling the Result as appropriate for its context.

Here's how it could look in src/config/mod.rs:

// in src/config/mod.rs
pub async fn get_versions_needed_by_tracked_configs(
    config: &Arc<Config>,
) -> eyre::Result<std::collections::HashSet<(String, String)>> {
    let mut needed = std::collections::HashSet::new();
    let tracked_configs = config.get_tracked_config_files().await?;
    for cf in tracked_configs.values() {
        let trs = cf.to_tool_request_set()?;
        let mut ts = Toolset::from(trs);
        ts.resolve(config).await?;
        for (_, tv) in ts.list_current_versions() {
            needed.insert((tv.ba().short.to_string(), tv.tv_pathname()));
        }
    }
    Ok(needed)
}

Then, in upgrade, you can call it and handle the error gracefully:

// in src/cli/upgrade.rs inside `upgrade` method
let versions_needed_by_tracked = crate::config::get_versions_needed_by_tracked_configs(config)
    .await
    .unwrap_or_else(|e| {
        debug!("Failed to get needed versions from tracked configs: {e}");
        Default::default()
    });

This change would centralize the logic and make the codebase cleaner. The prune command could then also be refactored to use this shared function.

@github-actions
Copy link

github-actions bot commented Jan 31, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.12 x -- echo 20.9 ± 0.2 20.4 23.1 1.00
mise x -- echo 21.7 ± 0.3 21.0 23.4 1.04 ± 0.02

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.12 env 20.4 ± 0.6 19.8 28.0 1.00
mise env 21.3 ± 0.4 20.5 24.3 1.04 ± 0.04

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.12 hook-env 21.2 ± 0.2 20.6 22.4 1.00
mise hook-env 21.9 ± 0.6 21.2 30.7 1.03 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.1.12 ls 19.0 ± 0.2 18.5 20.3 1.00
mise ls 19.8 ± 0.4 18.8 21.6 1.04 ± 0.03

xtasks/test/perf

Command mise-2026.1.12 mise Variance
install (cached) 113ms 114ms +0%
ls (cached) 73ms 72ms +1%
bin-paths (cached) 75ms 76ms -1%
task-ls (cached) 542ms 541ms +0%

jdx and others added 3 commits January 31, 2026 08:40
`mise upgrade` now checks tracked config files before uninstalling old
versions, consistent with how `mise prune` behaves. If a version is
still referenced by any tracked config, it will be kept instead of
being uninstalled.

This prevents the issue where upgrading a tool in one directory would
uninstall versions that are still needed by projects in other directories.

Fixes #7640

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix dry-run output to accurately reflect tracked config protection
- Propagate errors from get_versions_needed_by_tracked_configs (no silent failures)
- Extract shared helper function to toolset module to eliminate code duplication

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move the tracked config check to happen AFTER the upgrade and config
reset. This ensures versions resolve correctly - when `.tool-versions`
says `dummy 1`, it should resolve to the new version (1.1.0) after
upgrade, not the old version (1.0.0).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jdx jdx force-pushed the fix/upgrade-respect-tracked-configs branch from 7edc1b2 to 87f1ac2 Compare January 31, 2026 14:42
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON, but a Cloud Agent failed to start.

pub async fn get_versions_needed_by_tracked_configs(
config: &Arc<Config>,
) -> Result<std::collections::HashSet<(String, String)>> {
let mut needed = std::collections::HashSet::new();
Copy link

Choose a reason for hiding this comment

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

Inconsistent import style for HashSet

Low Severity

std::collections::HashSet is used with full path twice, while HashMap is imported at line 1. Other files in this module (helpers.rs, tool_request_set.rs, toolset_install.rs) import HashSet at the top. Add HashSet to the existing import on line 1 for consistency.

Fix in Cursor Fix in Web

@jdx jdx merged commit 9c30288 into main Jan 31, 2026
35 checks passed
@jdx jdx deleted the fix/upgrade-respect-tracked-configs branch January 31, 2026 14:57
mise-en-dev added a commit that referenced this pull request Feb 1, 2026
### 🚀 Features

- **(edit)** add interactive config editor (`mise edit`) by @jdx in
[#7930](#7930)
- **(lockfile)** graduate lockfiles from experimental by @jdx in
[#7929](#7929)
- **(task)** add support for usage values in task confirm dialog by
@roele in [#7924](#7924)
- **(task)** improve source freshness checking with edge case handling
by @jdx in [#7932](#7932)

### 🐛 Bug Fixes

- **(activate)** preserve ordering of paths appended after mise activate
by @jdx in [#7919](#7919)
- **(install)** sort failed installations for deterministic error output
by @jdx in [#7936](#7936)
- **(lockfile)** preserve URL and prefer sha256 when merging platform
info by @jdx in [#7923](#7923)
- **(lockfile)** add atomic writes and cache invalidation by @jdx in
[#7927](#7927)
- **(templates)** use sha256 for hash filter instead of blake3 by @jdx
in [#7925](#7925)
- **(upgrade)** respect tracked configs when pruning old versions by
@jdx in [#7926](#7926)

### 🚜 Refactor

- **(progress)** migrate from indicatif to clx by @jdx in
[#7928](#7928)

### 📚 Documentation

- improve clarity on uvx and pipx dependencies by @ygormutti in
[#7878](#7878)

### ⚡ Performance

- **(install)** use Kahn's algorithm for dependency scheduling by @jdx
in [#7933](#7933)
- use Aho-Corasick for efficient redaction by @jdx in
[#7931](#7931)

### 🧪 Testing

- remove flaky test_http_version_list test by @jdx in
[#7934](#7934)

### Chore

- use github backend instead of ubi in mise.lock by @jdx in
[#7922](#7922)

### New Contributors

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

## Summary
- Make `mise upgrade` check tracked config files before uninstalling old
versions
- Consistent with how `mise prune` behaves

## Problem
When running `mise upgrade` from one directory, it would uninstall old
versions of tools without checking if those versions are still needed by
projects in other directories (tracked configs).

For example:
1. Global config has `tiny = "latest"`
2. Project A has `tiny = "3.0.0"` (tracked)
3. Running `mise upgrade tiny` from home would upgrade to 3.1.0 and
**uninstall 3.0.0**
4. Project A can no longer use its pinned version

## Solution
Added a helper function `get_versions_needed_by_tracked_configs()` that:
1. Gets all tracked config files
2. Resolves each toolset to get concrete versions
3. Returns a set of (tool_name, version_pathname) pairs

Before uninstalling an old version, we now check if it's in this set and
skip uninstalling if so.

Fixes jdx#7640

## Test plan
- [x] Code compiles and passes linting
- [ ] Manual testing with tracked configs

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes uninstallation behavior in `mise upgrade` and centralizes
version-resolution logic across tracked configs; mistakes could leave
unused versions installed or, worse, still remove a needed version if
the tracked-config resolution misses cases.
> 
> **Overview**
> `mise upgrade` now checks all *tracked config files* before
uninstalling an old tool version, and skips uninstall when that version
is still needed by another tracked project.
> 
> This behavior is shared with `mise prune` via a new helper,
`get_versions_needed_by_tracked_configs()`, which resolves each tracked
config’s toolset and returns the concrete versions to keep (identified
by `(tool_short_name, tv_pathname)`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
87f1ac2. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

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

- **(edit)** add interactive config editor (`mise edit`) by @jdx in
[jdx#7930](jdx#7930)
- **(lockfile)** graduate lockfiles from experimental by @jdx in
[jdx#7929](jdx#7929)
- **(task)** add support for usage values in task confirm dialog by
@roele in [jdx#7924](jdx#7924)
- **(task)** improve source freshness checking with edge case handling
by @jdx in [jdx#7932](jdx#7932)

### 🐛 Bug Fixes

- **(activate)** preserve ordering of paths appended after mise activate
by @jdx in [jdx#7919](jdx#7919)
- **(install)** sort failed installations for deterministic error output
by @jdx in [jdx#7936](jdx#7936)
- **(lockfile)** preserve URL and prefer sha256 when merging platform
info by @jdx in [jdx#7923](jdx#7923)
- **(lockfile)** add atomic writes and cache invalidation by @jdx in
[jdx#7927](jdx#7927)
- **(templates)** use sha256 for hash filter instead of blake3 by @jdx
in [jdx#7925](jdx#7925)
- **(upgrade)** respect tracked configs when pruning old versions by
@jdx in [jdx#7926](jdx#7926)

### 🚜 Refactor

- **(progress)** migrate from indicatif to clx by @jdx in
[jdx#7928](jdx#7928)

### 📚 Documentation

- improve clarity on uvx and pipx dependencies by @ygormutti in
[jdx#7878](jdx#7878)

### ⚡ Performance

- **(install)** use Kahn's algorithm for dependency scheduling by @jdx
in [jdx#7933](jdx#7933)
- use Aho-Corasick for efficient redaction by @jdx in
[jdx#7931](jdx#7931)

### 🧪 Testing

- remove flaky test_http_version_list test by @jdx in
[jdx#7934](jdx#7934)

### Chore

- use github backend instead of ubi in mise.lock by @jdx in
[jdx#7922](jdx#7922)

### New Contributors

- @ygormutti made their first contribution in
[jdx#7878](jdx#7878)
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