Skip to content

fix(backend): allow unresolved latest opt-in#9401

Merged
jdx merged 1 commit intomainfrom
codex/fix-go-latest-stale-dirs
Apr 26, 2026
Merged

fix(backend): allow unresolved latest opt-in#9401
jdx merged 1 commit intomainfrom
codex/fix-go-latest-stale-dirs

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 26, 2026

Summary

Fixes latest resolution for backends that can install an unresolved selector when their remote version list is empty.

minimum_release_age cannot be evaluated when a backend has no version candidates, but falling back to literal latest for every empty-list backend is unsafe: some backends require concrete versions and must continue failing rather than creating literal latest/ installs. This change keeps the fallback in the central resolver but makes it opt-in through Backend::unresolved_latest_version().

The resolver now only falls back when the backend’s unfiltered remote version list is empty and the backend explicitly provides an unresolved selector. If versions exist but are all filtered out by minimum_release_age, mise still reports no matching version, preserving the age gate.

pipx opts in only for git-backed requests, where latest is an installable selector. Its remote version listing remains honest: GitHub repos with no releases return [] from ls-remote instead of reporting latest as a release. Tests also assert GitHub release timestamps are preserved for date filtering.

Validation

  • cargo fmt --check
  • cargo test backend::pipx::tests
  • cargo build
  • mise run test:e2e e2e/backend/test_fuzzy_versions_do_not_install_literal_dirs
  • clean-cache config check: pipx:a13xp0p0v/kernel-hardening-checker@latest resolves to latest without minimum_release_age
  • clean-cache config check: pipx:a13xp0p0v/kernel-hardening-checker@latest with minimum_release_age = "7d" resolves to latest
  • clean-cache guard check: pipx:psf/black@latest with minimum_release_age = "1970-01-01" still reports no matching version instead of falling back to HEAD
  • clean-cache ls-remote pipx:a13xp0p0v/kernel-hardening-checker --json returns []
  • pre-commit hook during amend, including cargo check --all-features

This PR description was generated by an AI coding assistant.


Note

Medium Risk
Changes central @latest resolution behavior by allowing an unresolved selector fallback for opted-in backends when remote version listing is empty, which could affect install outcomes for latest requests. Scope is limited via explicit backend opt-in and only triggers in non-offline mode with no remote versions returned.

Overview
Fixes @latest resolution for backends that can install an unresolved selector when no remote versions are discoverable.

Adds a new Backend::unresolved_latest_version() opt-in hook and updates ToolVersion::resolve_version to fall back to that value only when the backend’s unfiltered remote version list is empty (and only when not offline), preserving failure behavior when versions exist but don’t match date/age filters.

Updates pipx to opt in for git-based requests by returning latest as an installable unresolved selector, while making its remote version listing for non-GitHub git sources return an empty list (instead of a synthetic latest version), and adds tests for GitHub release->version mapping and timestamp preservation.

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

@jdx jdx marked this pull request as ready for review April 26, 2026 12:59
@jdx jdx force-pushed the codex/fix-go-latest-stale-dirs branch from 0f02dd4 to 9c9c938 Compare April 26, 2026 13:02
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 improves Go module version resolution by implementing a fallback to fetch "@latest" metadata for modules without tagged versions and refactors the pipx backend to default to a "latest" version when no GitHub releases are found. It also updates the runtime symlink migration to version 2, which includes logic to skip temporary "latest" labels and refreshes the installation state after migration. I have no feedback to provide.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR fixes @latest resolution for pipx backends that return an empty remote version list (e.g. git repos with no GitHub releases) by introducing a new opt-in Backend::unresolved_latest_version() hook and a fallback branch in the latest resolver. The age-gate (minimum_release_age) is correctly preserved: the fallback only fires when list_remote_versions (no date filter applied) returns an empty list, so repos that have releases but are all too recent still produce a no_versions_found error rather than falling through to "latest".

Confidence Score: 5/5

Safe to merge; the age-gate logic is correctly preserved and the new fallback is tightly scoped to the empty-list case.

No P0 or P1 issues found. The two prior-review concerns (GitHub URL opt-in scope, before_date regression) are handled correctly by the unfiltered list_remote_versions guard. Only a trivial blank-line style nit remains.

No files require special attention.

Important Files Changed

Filename Overview
src/backend/mod.rs Adds the unresolved_latest_version() opt-in hook to the Backend trait with a correct default of None; logic and safety gating are sound. Minor: missing blank line before the following method.
src/backend/pipx.rs Extracts versions_from_github_releases helper, changes non-GitHub git remote listing to return [], and provides unresolved_latest_version() for all PipxRequest::Git variants. New unit tests correctly assert ordering and created_at propagation. The age-gate concern from previous review is correctly handled by the resolver checking the unfiltered list before invoking the hook.
src/toolset/tool_version.rs Adds the fallback branch that calls list_remote_versions (no date filter) and uses unresolved_latest_version() only when the unfiltered list is empty. Age-gate is correctly preserved: repos with releases (even all-recent ones) produce a non-empty unfiltered list so the fallback is never reached. The second list_remote_versions call is served from the in-memory cache.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["resolve_version: v == latest"] --> B{latest_installed_version?}
    B -->|Some| DONE1[return installed version]
    B -->|None| C{is_offline?}
    C -->|Yes| ERR[no_versions_found error]
    C -->|No| D["latest_version with before_date"]
    D -->|Some| DONE2[return resolved version]
    D -->|None| E["list_remote_versions — unfiltered by date"]
    E --> F{list is empty?}
    F -->|No| ERR
    F -->|Yes| G{unresolved_latest_version?}
    G -->|None| ERR
    G -->|Some v| DONE3[return unresolved selector]
Loading

Fix All in Claude Code

Reviews (6): Last reviewed commit: "fix(backend): allow unresolved latest op..." | Re-trigger Greptile

@jdx jdx force-pushed the codex/fix-go-latest-stale-dirs branch from 9c9c938 to addf3e8 Compare April 26, 2026 13:13
@jdx jdx changed the title fix(pipx): resolve github repos without releases fix(backend): allow unresolved latest fallback Apr 26, 2026
Comment thread src/backend/pipx.rs
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 addf3e8. Configure here.

Comment thread src/toolset/tool_version.rs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 26, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 x -- echo 17.5 ± 0.4 16.6 19.3 1.00
mise x -- echo 17.6 ± 0.3 16.8 18.8 1.01 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 env 17.5 ± 0.5 16.5 19.3 1.00
mise env 17.9 ± 0.5 16.9 21.0 1.02 ± 0.04

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 hook-env 17.9 ± 0.6 16.8 24.5 1.00
mise hook-env 18.3 ± 0.4 17.0 21.0 1.02 ± 0.04

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 ls 18.5 ± 0.5 17.4 20.2 1.00
mise ls 19.1 ± 0.6 17.9 27.8 1.03 ± 0.04

xtasks/test/perf

Command mise-2026.4.22 mise Variance
install (cached) 121ms 126ms -3%
ls (cached) 67ms 68ms -1%
bin-paths (cached) 67ms 67ms +0%
task-ls (cached) 709ms 704ms +0%

@jdx jdx force-pushed the codex/fix-go-latest-stale-dirs branch from addf3e8 to 0624feb Compare April 26, 2026 14:02
@jdx jdx changed the title fix(backend): allow unresolved latest fallback fix(backend): ignore age filter for empty version lists Apr 26, 2026
Comment thread src/toolset/tool_version.rs Outdated
@jdx jdx force-pushed the codex/fix-go-latest-stale-dirs branch 2 times, most recently from b17aabf to 933eb4f Compare April 26, 2026 14:18
@jdx jdx changed the title fix(backend): ignore age filter for empty version lists fix(backend): allow unresolved latest opt-in Apr 26, 2026
@jdx jdx force-pushed the codex/fix-go-latest-stale-dirs branch from 933eb4f to 483868d Compare April 26, 2026 14:32
@jdx jdx merged commit 580635a into main Apr 26, 2026
50 of 52 checks passed
@jdx jdx deleted the codex/fix-go-latest-stale-dirs branch April 26, 2026 15:07
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)
jdx added a commit that referenced this pull request May 2, 2026
…ends (#9548)

## Summary

Backends that opt into installing an unresolved `latest` selector (today
only `pipx` for git-backed requests) legitimately return an empty remote
version list when their source has no discoverable versions — e.g. a
GitHub repo with no releases (only tags). The hard-error path was
already addressed in #9401 by falling back to the unresolved selector.

What's still left is noise: every command that touches such a tool emits
3–12 repeated `mise WARN No versions found for …` warnings. The
repetition comes from #9444 (don't cache empty version lists) — every
separate call site that asks for remote versions in the same process
re-fetches and re-warns. Underlying GitHub API calls are still
deduplicated by `RELEASE_CACHE`, so this is purely log noise, not extra
HTTP traffic.

This change suppresses the warning when the backend opts into
`unresolved_latest_version()`. For those backends an empty list is the
expected steady state, not an error worth flagging. Other backends still
warn as before, so the warning continues to catch real problems (typo'd
repos, configs pointing at the wrong place).

Refs #9381.

## Reproduction

\`\`\`toml
# ~/.config/mise/conf.d/tools.toml
[tools]
"pipx:a13xp0p0v/kernel-hardening-checker" = "latest"
\`\`\`

`a13xp0p0v/kernel-hardening-checker` has 0 GitHub releases (14 tags).
Before this PR:

\`\`\`
$ mise current
mise WARN  No versions found for pipx:a13xp0p0v/kernel-hardening-checker
mise WARN  No versions found for pipx:a13xp0p0v/kernel-hardening-checker
mise WARN  No versions found for pipx:a13xp0p0v/kernel-hardening-checker
pipx:a13xp0p0v/kernel-hardening-checker latest
\`\`\`

After:

\`\`\`
$ mise current
pipx:a13xp0p0v/kernel-hardening-checker latest
\`\`\`

`mise ls`, `mise outdated`, and `mise install` are similarly quiet now.

## Test plan

- [x] Manual repro with the discussion's tool — all warnings gone for
`mise ls`, `mise current`, `mise outdated`, `mise install`
- [x] `mise run lint`
- [x] `cargo build`

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: only changes warning emission logic when a backend opts into
`unresolved_latest_version()`, with no effect on version fetching or
installation behavior.
> 
> **Overview**
> Suppresses repeated `No versions found` warnings when
`list_remote_versions_with_info` returns an empty list for backends that
support installing an unresolved `latest` selector (via
`unresolved_latest_version()`). Other backends still warn on empty
remote version lists as before.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9ca20e5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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