Skip to content

fix(install): refresh latest before installing missing tools#9545

Merged
jdx merged 7 commits intomainfrom
codex/fix-aqua-latest-cache
May 2, 2026
Merged

fix(install): refresh latest before installing missing tools#9545
jdx merged 7 commits intomainfrom
codex/fix-aqua-latest-cache

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented May 2, 2026

What changed

  • Added a scoped cache-bypass path for install-time version resolution.
  • When installing an unresolved latest request, mise now refreshes remote version caches if no installed version can satisfy the request.
  • Added a cache test covering fresh on-disk cache bypass behavior.

Why

A stale remote-versions cache could make mise use <tool> install an older cached latest when the tool was not already installed. The install path should only reuse cache for latest when it can reuse an installed version; otherwise it should check upstream before choosing what to install.

Validation

  • cargo check -q
  • cargo test -q cache::tests
  • cargo run -q -- use --dry-run --pin --path "$tmp/mise.toml" hk resolved [email protected]

Note

Medium Risk
Touches core version-resolution and caching paths; mistakes could cause extra network calls or incorrect version selection across multiple backends, though the refresh is narrowly scoped and has added test coverage.

Overview
Fixes install-time resolution so selectors that depend on fresh upstream data (notably latest, sub-N:latest, and some prefix requests) can bypass stale remote_versions cache before choosing what to install.

This introduces explicit refresh plumbing (ResolveOptions.refresh_remote_versions, Backend::list_remote_versions*_with_refresh, and CacheManager::refresh_async), updates resolution call sites to use it, and adds a scoped install-time heuristic (should_refresh_remote_versions) to trigger refresh only when needed.

Also adjusts backend overrides (conda, core java) to hook the new refresh-aware method, adds a cache refresh test, and hardens the npm package-manager e2e test to be deterministic across environments (explicitly pin npm manager, allow npm flag variations, and bypass MISE_MINIMUM_RELEASE_AGE when installing aube).

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

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 a cache bypass mechanism to ensure that 'latest' version resolutions fetch fresh data instead of relying on potentially stale local caches. It implements a CacheBypassGuard using a global AtomicUsize to track the bypass state and integrates this into the tool installation logic. Feedback indicates that using a global atomic for this state introduces side effects across concurrent tasks, which could lead to unnecessary cache bypasses and performance degradation during parallel installations. It is recommended to use tokio::task_local! to isolate the bypass state to specific tasks.

Comment thread src/cache.rs Outdated
Comment on lines +47 to +62
static CACHE_BYPASS_DEPTH: AtomicUsize = AtomicUsize::new(0);

struct CacheBypassGuard;

impl CacheBypassGuard {
fn new() -> Self {
CACHE_BYPASS_DEPTH.fetch_add(1, Ordering::SeqCst);
Self
}
}

impl Drop for CacheBypassGuard {
fn drop(&mut self) {
CACHE_BYPASS_DEPTH.fetch_sub(1, Ordering::SeqCst);
}
}
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 use of a global AtomicUsize for CACHE_BYPASS_DEPTH introduces a global side effect that affects all concurrent tasks. In mise, where multiple tools are often installed or resolved in parallel, one task triggering a cache bypass (e.g., for a latest version) will cause all other concurrent resolution tasks to also bypass their respective caches. This can lead to redundant network requests and performance degradation during parallel installs.

Consider using tokio::task_local! to manage this state. This would ensure the bypass is only active for the specific task (and its children) that requires it, maintaining the efficiency of parallel operations for other tools.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 2, 2026

Greptile Summary

This PR fixes stale-cache installs for latest and unresolved prefix selectors by introducing a refresh_remote_versions flag in ResolveOptions, a scoped refresh_async method on CacheManager, and a should_refresh_remote_versions helper that enables per-request cache bypass at install time. The approach correctly replaces what was previously a process-global bypass counter, scoping the bypass to individual tool resolutions via the resolve options struct threaded through the call chain.

Confidence Score: 5/5

Safe to merge; no P0/P1 issues found — only minor P2 clarity notes.

All findings are P2 (style/clarity). The core logic — scoped refresh_remote_versions flag, CacheManager::refresh_async, and should_refresh_remote_versions — is correct. The previous global-bypass-counter concern is resolved. Tests cover the cache refresh semantics.

No files require special attention.

Important Files Changed

Filename Overview
src/toolset/toolset_install.rs Adds should_refresh_remote_versions and wires refresh_remote_versions flag into install-time resolution; logic is correct but the early-return guard when the flag is already true is a latent readability/maintenance issue.
src/backend/mod.rs Introduces list_remote_versions_with_refresh, list_remote_versions_with_info_with_refresh, and latest_version_with_refresh APIs; refactors latest_version control flow correctly; refresh is intentionally bypassed by the latest_stable_version fast path.
src/cache.rs Adds refresh_async which unconditionally fetches, writes to disk, and replaces both in-memory OnceCell instances; test correctly validates in-memory propagation after refresh.
src/toolset/tool_version.rs Threads refresh_remote_versions through ResolveOptions and all resolution code paths; propagation is complete and consistent across all ToolRequest variants.
src/backend/conda.rs Override migrated from list_remote_versions_with_info to list_remote_versions_with_info_with_refresh; _refresh flag correctly ignored since conda always queries channel directly.
src/plugins/core/java.rs Override migrated to list_remote_versions_with_info_with_refresh; _refresh flag correctly ignored since Java's GA/EA fetch already handles its own freshness.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["install_single_tool(tr, opts)"] --> B["should_refresh_remote_versions(tr, backend, opts)"]
    B --> C{ToolRequest type?}
    C -->|"Version('latest')"| D["refresh = true"]
    C -->|"Prefix, no installed match"| D
    C -->|"Sub('latest')"| D
    C -->|"Ref / Path / System"| E["refresh = false"]
    C -->|"already set / offline"| E
    D --> F["resolve_options.refresh_remote_versions = true"]
    E --> G["resolve_options unchanged"]
    F --> H["tr.resolve(config, resolve_options)"]
    G --> H
    H --> I["latest_version_with_refresh(refresh)"]
    I --> J{"resolved_query == 'latest' && no before_date?"}
    J -->|yes| K["latest_stable_version() — refresh ignored here"]
    K -->|Some| L["return version"]
    K -->|None| M["latest_version_for_query(..., refresh)"]
    J -->|no| M
    M --> N["list_remote_versions_with_info_with_refresh(refresh)"]
    N --> O{refresh?}
    O -->|true| P["CacheManager::refresh_async — bypass disk + memory cache"]
    O -->|false| Q["CacheManager::get_or_try_init_async — use cache if fresh"]
    P --> R["write to disk, replace OnceCell"]
    Q --> R
Loading

Fix All in Claude Code

Reviews (8): Last reviewed commit: "fix(test): make npm e2e robust to npm ve..." | Re-trigger Greptile

Comment thread src/cache.rs Outdated
Comment on lines +47 to +65
static CACHE_BYPASS_DEPTH: AtomicUsize = AtomicUsize::new(0);

struct CacheBypassGuard;

impl CacheBypassGuard {
fn new() -> Self {
CACHE_BYPASS_DEPTH.fetch_add(1, Ordering::SeqCst);
Self
}
}

impl Drop for CacheBypassGuard {
fn drop(&mut self) {
CACHE_BYPASS_DEPTH.fetch_sub(1, Ordering::SeqCst);
}
}

fn cache_bypass_enabled() -> bool {
CACHE_BYPASS_DEPTH.load(Ordering::SeqCst) > 0
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.

P1 Global bypass counter leaks to unrelated concurrent tasks

CACHE_BYPASS_DEPTH is a process-global atomic. install_single_tool is spawned as parallel tokio tasks (via jset.spawn), so when task A increments this counter for its latest resolution, every other concurrent task in the process — installing different tools, reading plugin metadata, etc. — will also bypass their cache freshness checks for the entire duration of the await. This is broader than intended: the bypass should only affect the specific resolution call, not all cache reads process-wide.

A tokio task_local! scoped value (or passing a bool context through the resolve call chain) would scope the bypass to the individual async task rather than the whole process.

Fix in Claude Code

Comment thread src/cache.rs Outdated
@jdx jdx force-pushed the codex/fix-aqua-latest-cache branch from 9e1a1dd to 46c8f01 Compare May 2, 2026 15:29
Comment thread src/toolset/tool_version.rs
Comment thread src/cache.rs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 x -- echo 22.9 ± 0.6 21.9 26.3 1.01 ± 0.04
mise x -- echo 22.8 ± 0.5 21.8 24.8 1.00

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 env 22.4 ± 1.0 20.8 28.3 1.00
mise env 22.9 ± 0.7 21.6 24.6 1.02 ± 0.06

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 hook-env 22.9 ± 0.7 21.6 25.5 1.00
mise hook-env 24.1 ± 0.8 22.2 30.9 1.06 ± 0.05

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.28 ls 20.6 ± 1.0 18.7 31.1 1.00
mise ls 21.3 ± 1.1 19.5 25.1 1.03 ± 0.07

xtasks/test/perf

Command mise-2026.4.28 mise Variance
install (cached) 157ms 158ms +0%
ls (cached) 80ms 83ms -3%
bin-paths (cached) 86ms 86ms +0%
task-ls (cached) 845ms 828ms +2%

Comment thread src/toolset/toolset_install.rs
Comment thread src/toolset/toolset_install.rs Outdated
jdx and others added 3 commits May 2, 2026 14:16
- resolve_sub now uses latest_version_refresh when opts.refresh_remote_versions
  is set, so `sub-N:latest` benefits from the same stale-cache fix as `latest`.
- CacheManager::refresh_async resets the in-memory cells after writing fresh
  data so subsequent non-refresh reads observe the new value instead of a
  stale previously-initialized one.
- should_refresh_remote_versions no longer triggers a refresh for fully-pinned
  ToolRequest::Version selectors (e.g. `[email protected]`). The cached list is
  equally accurate for checking whether an exact version exists, and the only
  selector whose meaning truly depends on freshness is `latest`.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Several review comments highlighted the same root issue: the API exposed
paired `foo` / `foo_refresh` methods, which forced every caller to write
`if opts.refresh_remote_versions { foo_refresh } else { foo }` and made
the resolution code hard to read.

- Replace `list_remote_versions_refresh`, `list_remote_versions_with_info_refresh`,
  and `latest_version_refresh` with single `*_with_refresh(refresh: bool)`
  methods. The cached convenience methods (`list_remote_versions`,
  `latest_version`, etc.) become thin wrappers passing `refresh = false`.
- Remove the five duplicated if/else branches in tool_version.rs by
  threading `opts.refresh_remote_versions` directly into the unified
  methods.
- Simplify `should_refresh_remote_versions`: drop the `latest_versions`
  early-return that contradicted the selective match block (greptile P1)
  and drop the now-unused `backend` parameter. The match cleanly captures
  which selectors actually depend on remote freshness.
- Tighten `refresh_async`'s in-memory cell update with `OnceCell::with_value`
  and `tokio::sync::OnceCell::new_with`.

Net -47 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@jdx jdx force-pushed the codex/fix-aqua-latest-cache branch from ca95404 to 2ef8bcf Compare May 2, 2026 19:21
The previous refactor skipped the `latest_stable_version` fast path on
refresh, reasoning that callers wanted to bypass any cache. That was
wrong: the fast path queries canonical upstream endpoints (GitHub's
`/releases/latest`, npm dist tags) which are not the cache we're trying
to bypass. Skipping it actually returned *older* results because the
fallback path goes through the versions-host cache (mise-versions.jdx.dev),
which can lag behind the upstream registry by hours.

This caused e2e/backend/test_npm_package_manager to fail: with
`mise use aube@latest` and refresh enabled, the versions host returned
v1.6.0 (which has no linux-x64 asset) instead of GitHub's actual latest
v1.6.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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 ed866d3. Configure here.

Comment thread src/backend/mod.rs
jdx and others added 2 commits May 2, 2026 15:00
…aths

The earlier refactor's call chain bypassed conda's and java's overrides
on `list_remote_versions_with_info` whenever resolution went through the
new `_with_refresh` path. Conda needs channel-aware listing (its
`channel` option affects available versions); Java keeps separate GA/EA
caches. Both backends always query upstream directly and don't care
about the shared remote-versions cache.

Move the overrides up one level to `list_remote_versions_with_info_with_refresh`
so all paths (cached and refresh-enabled) route through them. The
`_refresh` flag is ignored since both backends bypass the shared cache
unconditionally.

Also revert `list_remote_versions(config)` to chain through
`list_remote_versions_with_info(config)` rather than `_with_refresh(false)`,
preserving overrides on the simpler signature for any future backend.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The npm package-manager test sets MISE_MINIMUM_RELEASE_AGE=1d globally to
verify the flag plumbing on `mise install`. As a side effect, the filter
was also being applied to the installation of aube itself, which is just
a prerequisite for Test 4 (npm.package_manager=aube). When the maintainer
released v1.6.0 without linux-x64 assets ~24h before CI ran, and v1.6.1
(which restored linux assets) ~22h before, the date filter accepted
v1.6.0 as "latest" and excluded v1.6.1, breaking the install on linux.

Strip the env var for the aube install only — we want a working aube,
not an aged one — and let the rest of the test exercise the filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Comment thread src/toolset/toolset_install.rs Outdated
Two real bugs in the test that surface only on developer machines, not
in CI:

1. **Test 1 used the `auto` default for npm.package_manager**, which picks
   aube when aube is on PATH (including system installs like Homebrew).
   On CI no system aube exists so `auto` falls through to npm and the
   `--before` assertion passes. Anywhere else, `auto` finds aube and
   emits aube's `--minimum-release-age` instead, breaking the test. Pin
   to `MISE_NPM_PACKAGE_MANAGER=npm` so Test 1 actually tests npm.

2. **The `--before` assertion is npm-version-dependent.** mise emits
   `--min-release-age={days}` for npm 11.10.0+ (when the window is at
   least 1 day) and falls back to `--before {ts}` for older npm. CI's
   bundled npm happened to be old enough to use `--before`. Accept
   either flag with `assert_matches` so the test reflects what mise
   actually does.

Also address the greptile P1 on prefix refresh: skip the refresh when an
installed version already satisfies the prefix. The user can run
`mise upgrade` or pin to `@latest` to force a fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@jdx jdx merged commit aafe53f into main May 2, 2026
36 checks passed
@jdx jdx deleted the codex/fix-aqua-latest-cache branch May 2, 2026 21:09
mise-en-dev added a commit that referenced this pull request May 3, 2026
### 🚀 Features

- **(conda)** graduate conda backend out of experimental by @jdx in
[#9544](#9544)
- **(deps)** Add dart and flutter providers by @tjarvstrand in
[#9505](#9505)
- **(registry)** add neo4j by @mnm364 in
[#9525](#9525)
- **(registry)** add rustfs by @mnm364 in
[#9530](#9530)
- **(task)** support exclusion patterns in task sources by
@jlarmstrongiv in [#9496](#9496)
- **(vfox)** add stat function to lua file module by @esteve in
[#9497](#9497)

### 🐛 Bug Fixes

- **(backend)** flag regex prerelease versions by @jdx in
[#9500](#9500)
- **(backend)** mark -nightly/-canary/-experimental as prereleases by
@jdx in [#9523](#9523)
- **(backend)** suppress no-versions warning for unresolved-latest
backends by @jdx in [#9548](#9548)
- **(backend)** include dotnet prereleases from package flags by @jdx in
[#9551](#9551)
- **(backend)** scope PEP 440 prerelease detection to Python backends by
@jdx in [#9558](#9558)
- **(cargo)** Apply install_env during cargo install by @c22 in
[#9502](#9502)
- **(copr)** drop epel-9 chroots since rust >= 1.91 is unavailable by
@jdx in [#9484](#9484)
- **(github)** skip attestations on non-default api_url by @jdx in
[#9486](#9486)
- **(github)** retry ip allow list errors without auth by @risu729 in
[#9506](#9506)
- **(http)** update versions host tracking endpoint by @jdx in
[#9527](#9527)
- **(install)** don't warn for configured tools when version is passed
via CLI by @jdx in [#9522](#9522)
- **(install)** refresh latest before installing missing tools by @jdx
in [#9545](#9545)
- **(install)** don't cache nonexistent install paths by @jdx in
[#9553](#9553)
- **(lockfile)** don't propagate ad-hoc CLI overrides into the project
lockfile by @jdx in [#9562](#9562)
- **(plugin)** detect plugin types after cloning by @risu729 in
[#9540](#9540)
- **(release)** pass --no-git-checks to aube publish by @jdx in
[#9483](#9483)
- **(task)** convert PATH to MSYS Unix form when spawning POSIX shells
on Windows by @JamBalaya56562 in
[#9547](#9547)

### 📚 Documentation

- **(contributing)** require popularity check for registry PRs by @jdx
in
[7bbeebe](7bbeebe)
- **(watch)** update pitchfork domain to en.dev by @risu729 in
[#9536](#9536)
- document ghtkn GitHub token setup by @jdx in
[#9546](#9546)
- clarify registry backend acceptance policy by @jdx in
[#9543](#9543)
- Change exec command to use bash for variable echo by @kuboon in
[#9567](#9567)

### 🧪 Testing

- **(e2e)** run test-tool targets in parallel by @jdx in
[#9564](#9564)
- **(e2e)** run tests in parallel by @jdx in
[#9563](#9563)
- **(e2e)** bind-mount /tmp on disk and surface failed tests in CI
summary by @jdx in [#9570](#9570)
- **(tasks)** migrate test_task_help atask to usage field by @jdx in
[#9549](#9549)

### 📦️ Dependency Updates

- update fedora:45 docker digest to 8b838b3 by @renovate[bot] in
[#9507](#9507)
- update ghcr.io/jdx/mise:deb docker digest to f02194c by @renovate[bot]
in [#9509](#9509)
- update taiki-e/install-action digest to 7769b73 by @renovate[bot] in
[#9512](#9512)
- update ghcr.io/jdx/mise:alpine docker digest to 581f8a8 by
@renovate[bot] in [#9508](#9508)
- update rust crate ctor to v0.10.1 by @renovate[bot] in
[#9515](#9515)
- update ghcr.io/jdx/mise:rpm docker digest to a5c9655 by @renovate[bot]
in [#9510](#9510)
- update rust docker digest to a9cfb75 by @renovate[bot] in
[#9511](#9511)
- update rust crate age to v0.11.3 by @renovate[bot] in
[#9514](#9514)
- update rust crate jiff to v0.2.24 by @renovate[bot] in
[#9516](#9516)
- update dependency vitepress-plugin-tabs to ^0.9.0 by @renovate[bot] in
[#9518](#9518)
- update autofix-ci/action action to v1.3.4 by @renovate[bot] in
[#9513](#9513)
- update rust crate usage-lib to v3.2.1 by @renovate[bot] in
[#9517](#9517)
- update apple-actions/import-codesign-certs action to v7 by
@renovate[bot] in [#9519](#9519)
- update taiki-e/install-action digest to 51cd0b8 by @renovate[bot] in
[#9531](#9531)
- exclude taiki-e/install-action from renovate by @jdx in
[#9532](#9532)
- update rust crate blake3 to v1.8.5 by @renovate[bot] in
[#9533](#9533)

### 📦 Registry

- enable shellcheck on windows by @zeitlinger in
[#9487](#9487)
- add google-java-format by @zeitlinger in
[#9488](#9488)
- add expert
([aqua:expert-lsp/expert](https://github.com/expert-lsp/expert)) by
@AlternateRT in [#9498](#9498)
- update entry for checkmake by @eread in
[#9504](#9504)
- add systemctl-tui
([aqua:rgwood/systemctl-tui](https://github.com/rgwood/systemctl-tui))
by @2xdevv in [#9521](#9521)
- add codon by @3w36zj6 in
[#9538](#9538)
- add tool yr (backend:github:VirusTotal/yara-x) by @adam-moss in
[#9542](#9542)
- add tool betterleaks (backend:aqua/betterleaks/betterleaks) by
@adam-moss in [#9541](#9541)
- add `git-filter-repo` by @garysassano in
[#9550](#9550)
- add umoci
([aqua:opencontainers/umoci](https://github.com/opencontainers/umoci))
by @2xdevv in [#9555](#9555)
- add aqua backend for elixir-ls by @AlternateRT in
[#9557](#9557)
- deny inline backend options by @risu729 in
[#9565](#9565)

### Chore

- **(ci)** fail registry tests without summary by @jdx in
[#9559](#9559)
- **(ci)** use !cancelled() instead of always() for test-ci aggregator
by @jdx in [#9569](#9569)
- **(ci)** use namespace runners for ci jobs by @jdx in
[#9561](#9561)
- **(config)** deprecate shorthands_file setting by @risu729 in
[#9534](#9534)
- **(docs)** remove shrill.en.dev analytics script by @jdx in
[#9539](#9539)
- **(release)** replace bc with awk in release-plz star formatting by
@jdx in
[d7f177f](d7f177f)
- bump hk to 1.44.3 by @jdx in
[#9493](#9493)
- invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by @jdx in
[#9560](#9560)
- set dev profile debug to 1 by @jdx in
[#9572](#9572)

### New Contributors

- @kuboon made their first contribution in
[#9567](#9567)
- @AlternateRT made their first contribution in
[#9557](#9557)
- @2xdevv made their first contribution in
[#9555](#9555)
- @adam-moss made their first contribution in
[#9541](#9541)
- @jlarmstrongiv made their first contribution in
[#9496](#9496)
- @tjarvstrand made their first contribution in
[#9505](#9505)
jdx added a commit that referenced this pull request May 5, 2026
)

## Summary

- `should_refresh_remote_versions` (added in #9545) now bails on
`prefer_offline()` instead of just `offline()`.
- Fixes a regression in v2026.5.0 where shim auto-install of a `prefix:`
request fails after pre-warming with `mise install <tool>`.

## Why

In `prefer_offline` mode (set by shims, hook-env, activate, etc.) the
versions host is disabled (`versions_host.rs:71`). The post-#9545
install path was forcing a cache refresh whenever no installed version
matched a prefix request, which:

1. Bypassed the on-disk version cache populated by an earlier
non-prefer-offline command (e.g. `mise install terraform`).
2. Hit the versions host → returned `None` because of `prefer_offline`.
3. Fell through to the backend's own listing — for many backends this is
the GitHub releases API, which only returns the most recent ~30
versions.
4. Older but valid prefix queries (e.g. `terraform = \"prefix:1.10\"`)
failed with `no versions found for terraform`.

`prefer_offline()` already returns true whenever `offline()` is true, so
the guard's existing offline behavior is preserved; this just
additionally suppresses the cache-busting refresh on the fast paths
where it would always make things worse. The original #9545 motivation
(`mise use <tool>` getting a stale cached `latest`) is unaffected —
`mise use` doesn't run with `PREFER_OFFLINE` set.

Reported on commit aafe53f:
aafe53f#commitcomment-184356367

## Test plan

- [ ] `mise install terraform` (latest), then in a project with
`terraform = \"prefix:1.10\"` invoke a terraform shim — the older prefix
should resolve from cache and auto-install rather than erroring.
- [ ] `mise use terraform@latest` in a fresh shell still refreshes
upstream when the cached `latest` is stale (no regression on #9545's
fix).
- [ ] CI green.

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes install-time version resolution behavior for prefix/latest
selectors under `prefer_offline`, which could affect when remote caches
are refreshed and which versions get selected. Scope is small and
covered by a new e2e regression test, but it touches core install
resolution logic.
> 
> **Overview**
> Prevents install-time resolution from forcing a remote-versions cache
refresh when running in **`prefer_offline`** mode, so prefix-based
requests can resolve from the existing on-disk cache instead of falling
back to limited backend listings.
> 
> Adds an e2e regression test (`test_install_prefer_offline_no_refresh`)
that pre-warms the remote versions cache, pins a `prefix:` tool version,
forces `MISE_PREFER_OFFLINE=1`, and asserts the install succeeds without
re-running `list-all`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
741cf17. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
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