Skip to content

fix(prune): skip remote version resolution for tracked configs#9406

Merged
jdx merged 5 commits intomainfrom
claude/cool-agnesi-1971e2
Apr 26, 2026
Merged

fix(prune): skip remote version resolution for tracked configs#9406
jdx merged 5 commits intomainfrom
claude/cool-agnesi-1971e2

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 26, 2026

Summary

  • mise prune was resolving every tracked config's tool versions against the network (npm registry, Go proxy, GitHub API), which could hang on slow or failing registries — see #9405.
  • The remote calls were wasted work: prune only protects installed versions from deletion, and the resolver's early-return paths (latest_installed_version, list_installed_versions_matching) already match installed versions without hitting the network. If no installed version matches, the remote-resolved result can't appear in the deletion set anyway.
  • Adds an offline field to ResolveOptions, additive to Settings::offline(). Set to true in prune's path; left false for mise upgrade (which deliberately wants fresh remote data to decide what tracked configs would resolve to after an upgrade).
  • Fixes the latest resolver path: with offline + nothing installed, returns the unresolved "latest" string instead of erroring (safe — won't match any installed pathname).
  • Replaces the old workaround MISE_OFFLINE=1 mise prune --yes with default behavior.

Closes #9405.

Test plan

  • cargo check clean
  • mise run lint-fix clean
  • mise run test:unit (774 passed)
  • mise run test:e2e test_prune passes
  • Reviewer: in a directory with multiple tracked configs containing prefix/latest/range versions (e.g. node = "22", npm = "^11", a go: tool), MISE_DEBUG=1 mise prune --dry-run should no longer show DEBUG GET https://registry.npmjs.org/..., proxy.golang.org/..., or api.github.com/... requests during resolution.
  • Reviewer: MISE_DEBUG=1 mise upgrade --dry-run should still show remote version queries (verifying upgrade's behavior is unchanged).

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it changes version resolution behavior in offline paths (including latest/prefix/sub semantics), which can affect which tool versions are considered safe to delete during prune and after upgrade. Scope is contained to resolution options and tracked-config protection logic.

Overview
mise prune no longer performs network lookups when resolving tracked config requirements, by threading a new ResolveOptions.offline flag through tracked-config resolution.

This updates get_versions_needed_by_tracked_configs to accept an offline parameter (used by prune, not upgrade) and adds conservative protection for sub-N:latest when offline by keeping all installed versions for that backend.

Resolver behavior is adjusted so offline resolution can return unresolved specs for latest, prefix, and sub-N:latest instead of erroring or fetching remotes, while leaving normal upgrade/use/install paths explicitly offline: false.

Reviewed by Cursor Bugbot for commit 5316331. Bugbot is set up for automated code reviews on this repo. Configure here.

`mise prune` resolved every tracked config's tool versions against the
network (npm registry, Go proxy, GitHub API), which could hang on slow
or failing registries. The remote calls were wasted work: prune only
protects installed versions from deletion, and the resolver's
early-return paths already match installed versions without hitting
the network. If no installed version matches, the remote-resolved
result can't appear in the deletion set anyway.

Add an `offline` field to `ResolveOptions` and set it for prune's
resolution path. Upgrade keeps current behavior — it deliberately
checks "what would this resolve to after upgrade" against fresh
remote data.

Closes #9405

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
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 introduces an offline flag to ResolveOptions, enabling commands like mise prune to skip remote version resolution and operate entirely offline. The changes involve updating the get_versions_needed_by_tracked_configs function and various CLI commands to propagate this setting, as well as modifying the version resolution logic to return unresolved strings or prefixes when offline instead of erroring. Feedback suggests that the resolve_sub function was missed and should also implement an offline check to prevent network calls for specific version types.


let settings = Settings::get();
let is_offline = settings.offline();
let is_offline = settings.offline() || opts.offline;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The is_offline check is correctly implemented here for resolve_version, but it appears that resolve_sub (lines 451-468) was missed. If a tracked config uses a version like sub-1:latest, it will still attempt to hit the network via backend.latest_version at line 461. Consider adding a similar offline check to resolve_sub to ensure mise prune remains fully offline for all version types.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — fixed in 5171a18. resolve_sub now returns the unresolved request when opts.offline && v == "latest", mirroring the resolve_version latest path.

This comment was generated by an AI coding assistant.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR fixes mise prune hanging on slow or unreachable registries by introducing a ResolveOptions.offline field and setting it to true for prune's resolve path. The previous issues flagged in review (offline fallback for sub-N:latest and the resolve_sub early-exit) have been addressed: the code now conservatively over-protects all installed versions of a backend when sub-N:latest can't be safely resolved offline, and the resolve_prefix / resolve_version paths include proper offline fallbacks that produce non-matching dummy strings rather than erroring.

Confidence Score: 5/5

Safe to merge — all resolution paths are conservative (no incorrect deletions possible) and previously reported issues have been addressed

All offline fallbacks either return a concrete installed version (protecting it) or an unresolvable dummy string that can never match an installed pathname (safe no-op). The sub-N:latest edge case is handled conservatively by over-protecting all installed versions. Previously flagged P1 issues are resolved. No regressions to upgrade, install, or use paths since they all pass offline: false.

No files require special attention

Important Files Changed

Filename Overview
src/toolset/tool_version.rs Core change: adds offline field to ResolveOptions, threads it into resolve_version, resolve_prefix, and resolve_sub with correct offline fallbacks; previously reported sub-N:latest regression is addressed via over-protection in the caller
src/toolset/mod.rs Adds offline parameter to get_versions_needed_by_tracked_configs and conservatively over-protects all installed versions for ToolRequest::Sub { orig_version: "latest" } when offline, preventing inadvertent pruning of the active version
src/cli/prune.rs One-line change: passes offline=true to get_versions_needed_by_tracked_configs, fixing the root cause of network hangs during prune
src/cli/upgrade.rs Explicitly passes offline=false to both ResolveOptions and get_versions_needed_by_tracked_configs, preserving the existing network-enabled behavior for upgrade
src/cli/install.rs Adds offline: false to ResolveOptions initializer for completeness; no behavioral change
src/cli/use.rs Adds offline: false to ResolveOptions initializer for completeness; no behavioral change

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[mise prune] -->|offline=true| B[get_versions_needed_by_tracked_configs]
    B --> C[resolve_with_opts\nopts.offline=true]
    C --> D{version type?}

    D -->|v == latest| E{installed version\nexists?}
    E -->|yes| F[return installed version\n✓ protects it]
    E -->|no| G[return literal 'latest'\nno-op, safe]

    D -->|prefix e.g. 22| H{latest_versions=false\nAND installed match?}
    H -->|yes| I[return last installed\nmatching prefix\n✓ protects it]
    H -->|no| J[return raw prefix\nno-op, safe]

    D -->|sub-N:latest| K[return raw sub-N:latest\nno-op itself]
    K --> L[over-protect: add ALL\ninstalled versions of\nthis backend to needed set\n✓ conservative]

    D -->|exact version| M[resolve normally\nfrom installed cache]

    F & I & L & M --> N[needed set]
    G & J --> N

    N --> O[prune: remove from\nto_delete set]
    O --> P[delete remaining\nunneeded versions]
Loading

Reviews (4): Last reviewed commit: "fix(prune): over-protect for sub-N:lates..." | Re-trigger Greptile

Comment thread src/cli/install.rs
Comment on lines 233 to 237
use_locked_version: true,
latest_versions: true,
before_date: self.get_before_date()?,
offline: false,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant explicit offline: false

offline: false is already the value produced by Default::default(), so this explicit field is unnecessary in install.rs, upgrade.rs, and use.rs. The pattern is consistent across all three callers, which helps with readability, but if ResolveOptions::default() is ever changed to use a different default, these explicit falses would mask the inconsistency — consider leaving them as a documentation signal or adding a comment explaining they are intentionally overriding the default.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Leaving as-is — the surrounding initializers in install.rs, upgrade.rs, and use.rs already list every field of ResolveOptions explicitly rather than using ..Default::default(), so the explicit offline: false matches the local style and serves as the documentation signal you mentioned. Happy to revisit if jdx prefers the spread form.

This comment was generated by an AI coding assistant.

Comment thread src/toolset/tool_version.rs
jdx and others added 2 commits April 26, 2026 12:36
Per review feedback: `resolve_sub` called `backend.latest_version`
directly without honoring the offline flag, so a tracked config with
e.g. `sub-1:latest` would still hit the network during prune. Return
the unresolved request when offline — same safety argument as the
`latest` path: the literal string won't match any installed pathname.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per cursor review: the unresolved-string fallthrough in resolve_version's
`latest` path, resolve_prefix, and resolve_sub was triggering for global
MISE_OFFLINE=1 callers too — changing behavior for `mise upgrade --offline`
and `mise outdated` from "error" to "silently return literal selector",
which would compare incorrectly against installed versions.

Restrict the fallthrough to opts.offline (set only by prune), preserving
the original error behavior for global MISE_OFFLINE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/toolset/tool_version.rs
Per review: the previous offline guard returned the raw "sub-N:latest"
spec, which never matches any installed pathname in `to_delete`. Result:
the installed sub-N version was not protected and got pruned — the
opposite of intent.

Compute the sub from `latest_installed_version` (network-free) and
forward to `resolve_version` so its early-return path produces a
concrete installed version. Fall through to the raw spec only when
nothing is installed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1e335ae. Configure here.

Comment thread src/toolset/tool_version.rs
Per cursor review: applying version_sub to latest_installed_version
shifts the result one step too low, missing the actually-installed
sub-N target. resolve_sub now returns the raw `sub-N:latest` spec
in offline mode, and get_versions_needed_by_tracked_configs
detects this case and protects every installed version of the
backend. Over-protecting (some old versions stay) is the safe
failure mode vs. deleting the active version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx enabled auto-merge (squash) April 26, 2026 18:06
@jdx jdx merged commit b404138 into main Apr 26, 2026
36 checks passed
@jdx jdx deleted the claude/cool-agnesi-1971e2 branch April 26, 2026 18:09
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 x -- echo 24.3 ± 0.6 23.6 29.5 1.00
mise x -- echo 25.0 ± 0.6 23.9 29.1 1.03 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 env 24.2 ± 1.0 23.0 34.5 1.00
mise env 24.6 ± 0.8 23.5 35.8 1.02 ± 0.05

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 hook-env 24.4 ± 0.5 23.6 30.0 1.00
mise hook-env 25.0 ± 0.4 24.2 26.7 1.02 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 ls 24.7 ± 0.4 23.9 26.6 1.00
mise ls 25.3 ± 0.3 24.6 26.9 1.02 ± 0.02

xtasks/test/perf

Command mise-2026.4.22 mise Variance
install (cached) 171ms 177ms -3%
ls (cached) 82ms 84ms -2%
bin-paths (cached) 84ms 86ms -2%
task-ls (cached) 793ms 793ms +0%

jdx pushed a commit that referenced this pull request Apr 26, 2026
### 🚀 Features

- **(backend)** add global libc preference by @jdx in
[#9404](#9404)
- opt-in to pre-release versions for github and aqua backends by
@jakedgy in [#9329](#9329)

### 🐛 Bug Fixes

- **(backend)** allow unresolved latest opt-in by @jdx in
[#9401](#9401)
- **(install)** stop rewriting healthy runtime symlinks by @jdx in
[#9410](#9410)
- **(node)** route musl tarball URLs to unofficial-builds by @jdx in
[#9409](#9409)
- **(prune)** skip remote version resolution for tracked configs by @jdx
in [#9406](#9406)
- **(schema)** allow array values in tool additionalProperties by
@JP-Ellis in [#9400](#9400)

### 📦️ Dependency Updates

- bump communique to 1.1.2 by @jdx in
[#9402](#9402)

### 📦 Registry

- use aqua for rumdl by @scop in
[#9397](#9397)

### Chore

- **(ci)** improve pr-closer workflow by @jdx in
[#9403](#9403)
- **(release)** stop appending sponsor blurb when communique succeeds by
@jdx in [#9395](#9395)

### New Contributors

- @JP-Ellis made their first contribution in
[#9400](#9400)
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.

1 participant