Skip to content

fix(backend): key the remote version cache by listing tool options - #12164

Merged
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix/remote-version-cache-listing-options
Aug 19, 2026
Merged

fix(backend): key the remote version cache by listing tool options#12164
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix/remote-version-cache-listing-options

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

mise ls-remote answers with a stale list when a tool option that reshapes the listing differs between invocations. Starting from an empty cache dir:

$ mise ls-remote "github:Azure/azure-cli"                             # populates the cache
azure-cli-2.89.1                                                      # correct
$ mise ls-remote "github:Azure/azure-cli[version_prefix=azure-cli-]"
azure-cli-2.89.1                                                      # wrong, expected 2.89.1

and the other order is broken the same way — with a clean cache the prefixed run answers 2.89.1, and the unprefixed run then also answers 2.89.1 instead of azure-cli-2.89.1. Clearing the cache fixes it each time. Reproduced on 2026.8.8 linux-x64; it is not platform-specific.

Cause

The cached value genuinely depends on the option. All three release paths in _list_remote_versions filter tags by version_prefix and strip it before storing them, and api_url decides which host answered at all.

The key does not. get_remote_version_cache_with_context builds it from ba().cache_path.join("remote_versions.msgpack.z") plus, via CacheManagerBuilder, only mise's own version/features/profile/target and os/arch/libc. The one thing that can add to it is remote_version_cache_context, which defaults to None and which the github backend does not implement. ba().cache_path is derived from the opts-stripped short, so both invocations land on the same file — github-azure-azure-cli/remote_versions-e1b39.msgpack.z, identical hash suffix.

The information needed was already there and simply not wired up: backends declare which option keys reshape their listing through remote_version_listing_tool_option_keys, and the caller already computes has_local_version_listing_override and passes it in. Until now that declaration was used only to decide whether to skip the versions host.

Change

Digest the values of the declared keys into the cache context, combining with any context the backend supplies of its own. BTreeMap so the digest follows the values rather than the order the options were inserted in — the same shape as asdf's version_listing_cache_context.

Why this cannot change the versions host

The digest is produced only when has_local_version_listing_override is true, which is already the exact condition under which the host is skipped. In the decision chain, every case that newly gets a context was already returning false one branch further down, so use_versions_host is bit-for-bit unchanged. Anything without a local override keeps cache_context == None and takes precisely the path it takes today.

The two branches are swapped in this PR so each trace! still names its real cause. Without that, the has_local_version_listing_override arm would become statically dead — has_local ⟹ Some(context) — and every affected user would silently get the vaguer "local context" message. Both arms evaluate to the same literal false, and the cache_context.is_some() arm stays reachable for asdf/pipx/ruby, whose contexts come from remote_version_cache_context with has_local == false.

Checked rather than assumed:

  • node and python override get_remote_version_cache (not the _with_context form), which line 2064 routes around whenever a context exists. Neither declares listing keys, so neither can ever produce one here — has_any_key_from_sources is any() over an empty slice. Their mirror_url / python_compile cache keys still apply.
  • The three existing remote_version_cache_context overrides — asdf, pipx, ruby — declare no listing keys either, so the combining arm is unreachable today and their digests are byte-identical to before. It exists so a future backend declaring both does not silently drop one.
  • Registry-supplied values deliberately produce no context. They are identical for every user, so one entry is correct, and producing a context there would take the versions host away from the entire default population. BASE_CACHE_KEYS already includes the mise version, so a registry entry that changes a listing option cannot leave a stale entry across a release either.
  • The listing options are digested, not the selection options. listing_opts is resolved_opts.options() at both callers and is exactly what _list_remote_versions re-reads; selection_opts drives only the read-time prerelease filter and must not be digested, or the deliberate prerelease-superset caching would break.

Scope

The shared listing path, so github / gitlab / forgejo, ubi, spm, http and s3 are covered — http and s3 had the sharper version of this, where two different version_list_urls share one entry.

conda and java override list_remote_versions_with_info_and_options outright and never reach the shared cache, so they were never affected; I confirmed that by measurement for conda (switching channel in either order returns the correct list). vfox declares no keys.

Known, bounded limitation: get_string returns None for arrays and tables, so a non-scalar value digests as absent. All fourteen declared keys are scalars in practice, and the alternative panics internally on some values.

Tests

Four unit tests, no network:

  • test_listing_option_digest_is_stable_and_order_independentopts is insertion-ordered, the digest must not be.
  • test_listing_option_digest_tracks_declared_keys_only — two prefixes must differ; an install-time option that cannot change the listing must not split the cache.
  • test_remote_versions_cache_is_partitioned_by_listing_options — the regression test. Three mock backends sharing one cache directory: two option values get their own lists, and a third instance with the same value as the first reads that entry back without being asked for its own list, which is what shows the key follows the value and not the instance. Fails before this change and passes after.
  • test_declared_listing_keys_without_override_use_the_default_cache_entry — the other half: declaring listing options must not partition anything on its own. Asserts the list lands on the contextless handle, which is the state in which the versions host stays enabled.

The existing test_remote_version_listing_opts_ignore_registry_sources already covers Registry ⟹ has_local == false, which closes the chain to "host stays enabled".

The LatestBackend mock gains one &'static [&'static str] field defaulting to &[] — the trait default — so the existing tests are unaffected.

Verification

No local build was run — this machine cannot build mise, so CI has the last word on compilation, clippy and the tests. Everything above was verified by reading the code rather than by running it.

Once built, the repro at the top should give azure-cli-2.89.1 then 2.89.1 from a clean cache dir, with two remote_versions-*.msgpack.z files instead of one; and MISE_TRACE=1 mise ls-remote node should still print no "Skipping versions host" line.

Draft until CI is green.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: 2.1.235.

Summary by CodeRabbit

  • Bug Fixes
    • Improved remote version caching to keep results separate when listing settings, such as API URLs or version prefixes, differ.
    • Preserved shared caching when no custom listing settings are configured.
    • Improved logging for cases where the versions host is bypassed.

`ls-remote` returned a stale list when a tool option that reshapes the listing
changed between invocations. `github:Azure/azure-cli` and
`github:Azure/azure-cli[version_prefix=azure-cli-]` both resolve to
`<cache>/github-azure-azure-cli/remote_versions-e1b39.msgpack.z`, so whichever
ran first decided the answer for the other until the cache was cleared -- in
both directions.

The cached value is genuinely option-dependent: `_list_remote_versions` filters
tags by `version_prefix` and strips it before storing them, and `api_url`
decides which host answered. But the key is not: it is built from the tool's
cache path plus mise's own version/os/arch, and only `remote_version_cache_context`
can add to it, which the github backend does not implement.

Backends already declare which option keys reshape their listing, through
`remote_version_listing_tool_option_keys`. Digest the values of those keys into
the cache context so the entries are partitioned, combining with any context the
backend supplies of its own.

The digest is produced only when the values come from a local source -- config,
backend alias, inline arg, install manifest -- which is already the exact
condition under which the versions host is skipped, so the host decision is
unchanged: every case that newly gets a context was already short-circuited to
`false` one branch further down. Those two branches are swapped so each trace
message still names its real cause; both evaluate to the same `false`. A
registry-supplied value is identical for every user, so it deliberately produces
no context and the shared host stays available for the default case.

This is the shared listing path, so github, gitlab, forgejo, ubi, spm, http and
s3 are all covered. conda and java override the hook outright and were never
affected -- confirmed by measurement for conda.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 72ab1c11-9e9e-4ba9-9e0d-dd6533cc9b62

📥 Commits

Reviewing files that changed from the base of the PR and between 93a5786 and ce7f3ef.

📒 Files selected for processing (1)
  • src/backend/mod.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The backend now hashes local listing-relevant options and includes the digest in remote-version cache contexts. Tests cover deterministic hashing, cache separation for different values, cache reuse for identical values, and default-cache reuse without local overrides.

Changes

Remote-version cache partitioning

Layer / File(s) Summary
Listing-option digest contract
src/backend/mod.rs
The backend computes deterministic, order-independent digests from declared string-valued listing options. Unit tests cover value sensitivity and exclusion of unrelated options.
Cache context integration
src/backend/mod.rs
Remote-version cache contexts combine local listing-option digests with backend context. Versions-host bypass handling prioritizes local listing-option overrides.
Cache partitioning validation
src/backend/mod.rs
LatestBackend supports configurable listing-option keys. Async tests verify cache separation, cache reuse, and contextless default-cache reuse.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to ce7f3

This change partitions remote-version caches by listing options to prevent stale results across different invocations. No actionable merge-blocking risk remains beyond normal CI and review checks.

Suggested reviewers: jdx, risu729, marukome0743

Poem

I’m a rabbit with caches tucked under my ear,
Hashing each option so entries stay clear.
Different values hop to different rows,
Matching values share where the cache grows.
Default paths remain cozy and bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: including listing tool options in remote version cache keys.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR partitions remote-version cache entries by locally overridden options that affect version listing, while preserving the existing versions-host decision.

  • Adds a deterministic, order-independent digest of declared listing options.
  • Combines option-derived and backend-provided cache contexts.
  • Adds regression tests for cache partitioning, cache reuse, and the contextless default path.

Confidence Score: 5/5

The PR appears safe to merge, with no actionable correctness or security issues identified.

The new digest uses the same normalized effective values consumed by current listing backends, preserves contextless caching when no local override exists, and does not bypass any current backend-specific cache implementation.

Important Files Changed

Filename Overview
src/backend/mod.rs Adds listing-option-aware remote-version cache keys and focused tests without an identified correctness regression.

Reviews (1): Last reviewed commit: "fix(backend): key the remote version cac..." | Re-trigger Greptile

@JamBalaya56562
JamBalaya56562 marked this pull request as ready for review August 19, 2026 09:06
@jdx
jdx merged commit 43825ab into jdx:main Aug 19, 2026
30 checks passed
@JamBalaya56562
JamBalaya56562 deleted the fix/remote-version-cache-listing-options branch August 19, 2026 10:54
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