fix(prune): skip remote version resolution for tracked configs#9406
fix(prune): skip remote version resolution for tracked configs#9406
Conversation
`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>
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 SummaryThis PR fixes Confidence Score: 5/5Safe 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 No files require special attention Important Files Changed
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]
Reviews (4): Last reviewed commit: "fix(prune): over-protect for sub-N:lates..." | Re-trigger Greptile |
| use_locked_version: true, | ||
| latest_versions: true, | ||
| before_date: self.get_before_date()?, | ||
| offline: false, | ||
| }, |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
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>
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>
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 1e335ae. Configure here.
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>
Hyperfine Performance
|
| 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% |
### 🚀 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)

Summary
mise prunewas 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.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.offlinefield toResolveOptions, additive toSettings::offline(). Set totruein prune's path; leftfalseformise upgrade(which deliberately wants fresh remote data to decide what tracked configs would resolve to after an upgrade).latestresolver path: with offline + nothing installed, returns the unresolved"latest"string instead of erroring (safe — won't match any installed pathname).MISE_OFFLINE=1 mise prune --yeswith default behavior.Closes #9405.
Test plan
cargo checkcleanmise run lint-fixcleanmise run test:unit(774 passed)mise run test:e2e test_prunepassesnode = "22",npm = "^11", ago:tool),MISE_DEBUG=1 mise prune --dry-runshould no longer showDEBUG GET https://registry.npmjs.org/...,proxy.golang.org/..., orapi.github.com/...requests during resolution.MISE_DEBUG=1 mise upgrade --dry-runshould 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 duringpruneand afterupgrade. Scope is contained to resolution options and tracked-config protection logic.Overview
mise pruneno longer performs network lookups when resolving tracked config requirements, by threading a newResolveOptions.offlineflag through tracked-config resolution.This updates
get_versions_needed_by_tracked_configsto accept anofflineparameter (used by prune, not upgrade) and adds conservative protection forsub-N:latestwhen offline by keeping all installed versions for that backend.Resolver behavior is adjusted so
offlineresolution can return unresolved specs forlatest, prefix, andsub-N:latestinstead of erroring or fetching remotes, while leaving normalupgrade/use/installpaths explicitlyoffline: false.Reviewed by Cursor Bugbot for commit 5316331. Bugbot is set up for automated code reviews on this repo. Configure here.