fix(backend): key the remote version cache by listing tool options - #12164
Conversation
`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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe 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. ChangesRemote-version cache partitioning
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to 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: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
Greptile SummaryThe PR partitions remote-version cache entries by locally overridden options that affect version listing, while preserving the existing versions-host decision.
Confidence Score: 5/5The 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
Reviews (1): Last reviewed commit: "fix(backend): key the remote version cac..." | Re-trigger Greptile |
mise ls-remoteanswers with a stale list when a tool option that reshapes the listing differs between invocations. Starting from an empty cache dir: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 answers2.89.1instead ofazure-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_versionsfilter tags byversion_prefixand strip it before storing them, andapi_urldecides which host answered at all.The key does not.
get_remote_version_cache_with_contextbuilds it fromba().cache_path.join("remote_versions.msgpack.z")plus, viaCacheManagerBuilder, only mise's own version/features/profile/target and os/arch/libc. The one thing that can add to it isremote_version_cache_context, which defaults toNoneand which the github backend does not implement.ba().cache_pathis derived from the opts-strippedshort, 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 computeshas_local_version_listing_overrideand 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.
BTreeMapso the digest follows the values rather than the order the options were inserted in — the same shape as asdf'sversion_listing_cache_context.Why this cannot change the versions host
The digest is produced only when
has_local_version_listing_overrideis 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 returningfalseone branch further down, souse_versions_hostis bit-for-bit unchanged. Anything without a local override keepscache_context == Noneand 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, thehas_local_version_listing_overridearm 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 literalfalse, and thecache_context.is_some()arm stays reachable for asdf/pipx/ruby, whose contexts come fromremote_version_cache_contextwithhas_local == false.Checked rather than assumed:
nodeandpythonoverrideget_remote_version_cache(not the_with_contextform), which line 2064 routes around whenever a context exists. Neither declares listing keys, so neither can ever produce one here —has_any_key_from_sourcesisany()over an empty slice. Theirmirror_url/python_compilecache keys still apply.remote_version_cache_contextoverrides — 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.BASE_CACHE_KEYSalready includes the mise version, so a registry entry that changes a listing option cannot leave a stale entry across a release either.listing_optsisresolved_opts.options()at both callers and is exactly what_list_remote_versionsre-reads;selection_optsdrives 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,httpands3are covered —httpands3had the sharper version of this, where two differentversion_list_urls share one entry.condaandjavaoverridelist_remote_versions_with_info_and_optionsoutright and never reach the shared cache, so they were never affected; I confirmed that by measurement for conda (switchingchannelin either order returns the correct list).vfoxdeclares no keys.Known, bounded limitation:
get_stringreturnsNonefor 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_independent—optsis 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_sourcesalready coversRegistry ⟹ has_local == false, which closes the chain to "host stays enabled".The
LatestBackendmock 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.1then2.89.1from a clean cache dir, with tworemote_versions-*.msgpack.zfiles instead of one; andMISE_TRACE=1 mise ls-remote nodeshould 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