Skip to content

fix(go): Query module proxy directly for version resolution#8968

Merged
jdx merged 1 commit intojdx:mainfrom
c22:main
Apr 9, 2026
Merged

fix(go): Query module proxy directly for version resolution#8968
jdx merged 1 commit intojdx:mainfrom
c22:main

Conversation

@c22
Copy link
Copy Markdown
Contributor

@c22 c22 commented Apr 9, 2026

The go backend previously used go list -m -versions -json to resolve versions, which returns 200 with an empty version list for subpath packages (e.g. github.com/foo/bar/cmd/baz). This is indistinguishable from a real module with no tagged versions. This meant mise could never resolve "latest" for these tools, producing a "no latest version found" warning on every mise up and preventing version bumps via mise up -l.

This change replaces the version resolution with direct HTTP queries to the Go module proxy ($GOPROXY), matching the logic used by go install:

  1. Generate all path prefix candidates from the tool path
  2. Query $GOPROXY//@v/list for all prefixes in parallel
  3. The proxy returns 404 for non-modules (vs 200 for real modules), giving a clean signal to continue truncating
  4. For 200-but-empty responses, follow up with @latest to distinguish real modules using pseudo-versions from non-module paths
  5. Pick the longest prefix that resolved with tagged versions

This approach uses HTTP GETs, respects the GOPROXY env var (comma/pipe separators, direct, off), handles Go proxy module path encoding for uppercase letters, and falls back to go list -m -versions for GOPROXY=direct setups.

Manual testing

go:github.com/ankitpokhrel/jira-cli/cmd/jira

Subpath package (cmd/jira) within a module that has tagged releases. Previously, go list -m -versions returned 200 with empty versions for the subpath, and mise treated that as authoritative, never falling back to the root module github.com/ankitpokhrel/jira-cli which has versions v0.0.0 through v1.7.0.

Previously, mise up warned "no latest version found" on every run. Now resolves correctly via the proxy (subpath returns 404, root module returns versions).

go:github.com/GoogleCloudPlatform/scion/cmd/scion

Subpath package where the root module (github.com/GoogleCloudPlatform/scion) exists but has no tagged releases at all.

The proxy returns 200 with empty versions for the root, and @latest resolves to a pseudo-version. Same "no latest version found" warning as jira-cli. Now correctly identified as a real module with no tags, returns empty versions so go install ...@latest can resolve via pseudo-version at install time.

go:github.com/go-kratos/kratos/cmd/kratos/v2

Deep subpath with a /v2 major version suffix. This is a real Go submodule (has its own go.mod) but no tagged versions, it resolves via pseudo-version. The root module github.com/go-kratos/kratos has v1.x tags, which would be wrong to use. The proxy correctly returns 200-empty for the exact path, @latest confirms it's a real module, so we return empty and let go install handle it (no regression from #8823).

go:github.com/go-task/task/v3/cmd/task

Subpath under a /v3 major version module. The exact path returns 404, and truncation finds github.com/go-task/task/v3 (which is a proper prefix) with v3.x tags. Works correctly.

go:github.com/charmbracelet/vhs

Simple case, no subpath. Module root has tagged versions. Works as before (proxy returns versions directly for the exact path).

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 9, 2026

Greptile Summary

This PR replaces go list -m -versions version resolution with direct GOPROXY HTTP queries, correctly handling subpath packages (e.g. github.com/foo/bar/cmd/baz) that return 200-with-empty-body from go list — a signal previously indistinguishable from a real module with no releases. The implementation correctly generates prefix candidates, queries them in parallel, distinguishes pseudo-version-only modules via @latest, respects GOPROXY comma/pipe/off semantics, and encodes module paths per the proxy protocol.

Both issues flagged in previous review rounds are addressed: ProxyListResult::Error now returns Ok(None) to trigger the go-list fallback rather than silently continuing to shorter candidates, and off now uses break to truncate the proxy list rather than skipping the entry.

Confidence Score: 5/5

Safe to merge; the core fix is correct and both previously flagged P1 issues are addressed.

All remaining findings are P2. The Error fallthrough regression is fully fixed, the off keyword now uses break to correctly truncate the proxy list, and the parallel candidate-query logic handles all documented test cases correctly. The install_version_ comment/logic note is a pre-existing style issue that does not affect functional correctness for any user-visible scenario.

src/backend/go.rs — the install_version_ block at lines 96–107 fires for a case its comment does not describe, though functionally harmless.

Vulnerabilities

No security concerns identified. The HTTP requests are constructed from user-controlled GOPROXY URLs and tool path components, but this reflects the same trust model as the go toolchain itself. Module path encoding (encode_module_path) correctly handles uppercase characters to prevent path traversal via case-mangled segments.

Important Files Changed

Filename Overview
src/backend/go.rs Replaces go-list-based version resolution with direct GOPROXY HTTP queries; implements proxy parsing, module path encoding, and parallel candidate lookups — correctly addresses both the Error fallthrough and off-keyword issues from previous review rounds, with one remaining semantic edge case in install_version_

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[_list_remote_versions] --> B{parse_goproxy}
    B -->|proxies empty| C[fetch_go_module_versions exact path only]
    B -->|proxies present| D[fetch_proxy_versions]
    D --> E[Generate candidates longest to shortest prefix]
    E --> F[Query all candidates in parallel via JoinSet]
    F --> G[Sort results by idx process longest first]
    G --> H{Result for candidate?}
    H -->|non-empty versions| DONE[Return versions from this module]
    H -->|empty versions 200| I[query_proxy_latest]
    I -->|200 OK| J[Return Some empty vec]
    I -->|404 or 410| K[continue to shorter candidate]
    I -->|Error| FALLBACK[Return Ok None]
    H -->|NotFound| K
    H -->|Error| FALLBACK
    K --> H
    H -->|all exhausted| FALLBACK
    FALLBACK --> C
    C -->|non-empty| DONE
    C -->|empty or None| EMPTY[Return empty vec]
Loading

Reviews (3): Last reviewed commit: "fix(go): Query module proxy directly for..." | Re-trigger Greptile

Comment thread src/backend/go.rs Outdated
Comment thread src/backend/go.rs Outdated
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 refactors Go module version fetching to align with the standard $GOPROXY protocol, implementing support for proxy fallthrough logic and module path encoding. Feedback highlights a compilation risk due to the use of unstable Rust 'let_chains' syntax and suggests improving the reliability of version sorting by handling potential parsing errors from the Versioning library.

Comment thread src/backend/go.rs
Comment thread src/backend/go.rs
@c22
Copy link
Copy Markdown
Contributor Author

c22 commented Apr 9, 2026

Addressed PR feedback.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 9, 2026

Tip:

Greploop — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.

Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

The go backend previously used `go list -m -versions -json` to resolve versions, which returns 200 with an empty version list for subpath packages (e.g. github.com/foo/bar/cmd/baz). This is indistinguishable from a real module with no tagged versions. This meant mise could never resolve "latest" for these tools, producing a "no latest version found" warning on every `mise up` and preventing version bumps via `mise up -l`.

This change replaces the version resolution with direct HTTP queries to the Go module proxy ($GOPROXY), matching the logic used by `go install`':

1. Generate all path prefix candidates from the tool path
2. Query $GOPROXY/<path>/@v/list for all prefixes in parallel
3. The proxy returns 404 for non-modules (vs 200 for real modules), giving a clean signal to continue truncating
4. For 200-but-empty responses, follow up with @latest to distinguish real modules using pseudo-versions from non-module paths
5. Pick the longest prefix that resolved with tagged versions

This approach uses only cheap HTTP GETs, respects the GOPROXY env var (comma/pipe separators, direct, off), handles Go proxy module path encoding for uppercase letters, and falls back to `go list -m -versions` for GOPROXY=direct setups.
@jdx jdx merged commit d1a648b into jdx:main Apr 9, 2026
34 checks passed
mise-en-dev added a commit that referenced this pull request Apr 10, 2026
### 🚀 Features

- **(config)** add lockfile_platforms setting to restrict lockfile
platforms by @cameronbrill in
[#8966](#8966)
- **(sandbox)** support wildcard patterns in allow_env by @jdx in
[#8974](#8974)
- bump usage-lib v2 → v3 to render examples in task --help by @baby-joel
in [#8890](#8890)

### 🐛 Bug Fixes

- **(activate)** handle empty __MISE_FLAGS array with set -u on bash 3.2
by @jdx in [#8988](#8988)
- **(env)** add trace logging for module hook PATH diagnostics by @jdx
in [#8981](#8981)
- **(go)** Query module proxy directly for version resolution by @c22 in
[#8968](#8968)
- **(install)** render tera templates in tool postinstall hooks by @jdx
in [#8978](#8978)
- **(install)** add missing env vars to tool postinstall hooks by @jdx
in [#8977](#8977)
- **(task)** prevent hang when skipped task has dependents by @jdx in
[#8937](#8937)
- **(task)** invalidate dependent task sources when dependency runs by
@jdx in [#8975](#8975)
- **(task)** prevent deadlock when MISE_JOBS=1 with sub-task references
by @jdx in [#8976](#8976)
- **(task)** fetch remote task files before parsing usage specs by @jdx
in [#8979](#8979)
- **(task)** prevent panic when running parallel sub-tasks with
replacing output by @jdx in
[#8986](#8986)
- **(upgrade)** update lockfile and config when upgrading to specific
version by @jdx in [#8983](#8983)

### 📚 Documentation

- **(node)** remove "recommended for teams" from pin example by @jdx in
[b334363](b334363)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:alpine docker digest to 17a29f2 by
@renovate[bot] in [#8995](#8995)
- update docker/dockerfile:1 docker digest to 2780b5c by @renovate[bot]
in [#8994](#8994)

### New Contributors

- @baby-joel made their first contribution in
[#8890](#8890)
- @cameronbrill made their first contribution in
[#8966](#8966)
- @c22 made their first contribution in
[#8968](#8968)
jdx pushed a commit that referenced this pull request Apr 16, 2026
…9135)

The install method had a workaround that queried `go list -m -versions`
on the full tool path (including subpath) and, if no versions were
found, overrode tv.version to "latest". This was needed before #8968
because version resolution couldn't distinguish subpath packages from
tagless modules.

After #8968, proxy-based resolution handles both cases correctly:
subpaths get 404 (truncating to the root module), and tagless modules
get their pseudo-version from @latest.

The old workaround now fights with the correct resolution, it queries
the subpath (which always returns empty), overrides the version to
"latest", and installs into a `.../latest/` directory.

On the next `mise up`, version resolution finds e.g. `1.7.0` but
`.../1.7.0/` doesn't exist and causes a reinstall every time.

Apologies for missing this on the original PR!
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