Skip to content

fix(ruby): support build revisions for precompiled binaries in mise.lock#8900

Merged
jdx merged 4 commits intomainfrom
feat/build-revisions
Apr 4, 2026
Merged

fix(ruby): support build revisions for precompiled binaries in mise.lock#8900
jdx merged 4 commits intomainfrom
feat/build-revisions

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 4, 2026

Summary

  • When resolving precompiled Ruby binaries from jdx/ruby, prefer the latest build revision tag (e.g., 3.3.11-1 over 3.3.11)
  • Build revisions are tagged as {version}-{N} where N is an incrementing integer, representing rebuilds of the same upstream Ruby version
  • Once a build revision is locked in mise.lock, subsequent mise lock runs preserve it instead of upgrading to a newer build revision
  • This prevents mise.lock from breaking when binaries are rebuilt with different checksums

Background

The jdx/ruby repo now publishes build revision tags (e.g., 3.3.11 and 3.3.11-1). When binaries are rebuilt, the old release's checksums become stale. Previously, mise always fetched the exact version tag, so a lockfile created against 3.3.11 would break if the binaries were later republished as 3.3.11-1.

Changes

  • src/github.rs: Added get_release_with_build_revision() + shared pick_best_build_revision() helper that finds the highest {version}-N tag from releases; unit tests exercise the shared helper directly
  • src/plugins/core/ruby.rs: Added extract_build_revision_from_lock_platforms() to parse existing lockfile URLs for build revision tags; threads locked_build_revision through find_precompiled_asset_in_repo so locked revisions are preserved across mise lock runs, falling back to latest if the locked revision is deleted

Test plan

  • Unit tests for build revision selection logic (4 tests: highest wins, fallback to base, no match, non-numeric suffixes ignored)
  • Verify mise use ruby@3.3.11 installs from the -1 build revision release
  • Verify mise lock captures the build revision URL in the lockfile
  • Verify re-running mise lock preserves the existing build revision

🤖 Generated with Claude Code


Note

Medium Risk
Changes precompiled Ruby resolution to prefer and optionally pin GitHub release tags with {version}-N build revisions, which can affect which binaries/checksums are downloaded and locked. Risk is moderated by fallback behavior when a locked revision can’t be found and by added unit tests for revision selection.

Overview
Precompiled Ruby installs/locks now support GitHub build revision tags (e.g. 3.3.11-2) in repos like jdx/ruby, preferring the highest numeric -N tag over the base version.

Adds github::get_release_with_build_revision() (backed by pick_best_build_revision()) and unit tests, and updates the Ruby plugin to (1) extract an existing build revision from mise.lock URLs, (2) use that exact tag when present, and (3) fall back to the latest revision or base tag if the locked revision no longer exists.

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

When resolving precompiled Ruby binaries from jdx/ruby, prefer the
latest build revision tag (e.g., 3.3.11-1 over 3.3.11). This prevents
mise.lock from breaking when binaries are rebuilt with different
checksums while keeping the same upstream Ruby version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 mechanism to fetch the latest build revision for a GitHub release, specifically handling tags with incrementing integer suffixes (e.g., version-N). This is implemented in src/github.rs and integrated into the Ruby plugin to support precompiled binaries that might be rebuilt. Feedback focuses on optimizing the selection logic by avoiding redundant string allocations and refactoring duplicated code between the production function and the test suite into a shared helper.

Comment on lines +286 to +306
pub async fn get_release_with_build_revision(repo: &str, version: &str) -> Result<GithubRelease> {
let releases = list_releases(repo).await?;
let best = releases
.into_iter()
.filter(|r| {
r.tag_name == version
|| r.tag_name
.strip_prefix(&format!("{version}-"))
.is_some_and(|suffix| suffix.parse::<u32>().is_ok())
})
.max_by_key(|r| {
r.tag_name
.strip_prefix(&format!("{version}-"))
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0)
});
match best {
Some(release) => Ok(release),
None => get_release(repo, version).await,
}
}
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 current implementation performs redundant string formatting and allocations inside the filter and max_by_key closures. Pre-calculating the version prefix outside the iteration improves efficiency.

Additionally, this selection logic is duplicated in the test module (lines 741-759). Consider refactoring it into a shared private helper function within this file to improve maintainability and ensure tests are validating the production logic.

pub async fn get_release_with_build_revision(repo: &str, version: &str) -> Result<GithubRelease> {
    let releases = list_releases(repo).await?;
    let prefix = format!("{version}-");
    let best = releases
        .into_iter()
        .filter(|r| {
            r.tag_name == version
                || r.tag_name
                    .strip_prefix(&prefix)
                    .is_some_and(|suffix| suffix.parse::<u32>().is_ok())
        })
        .max_by_key(|r| {
            r.tag_name
                .strip_prefix(&prefix)
                .and_then(|s| s.parse::<u32>().ok())
                .unwrap_or(0)
        });
    match best {
        Some(release) => Ok(release),
        None => get_release(repo, version).await,
    }
}

src/github.rs Outdated
Comment on lines +741 to +759
fn select_best_build_revision(
releases: Vec<GithubRelease>,
version: &str,
) -> Option<GithubRelease> {
releases
.into_iter()
.filter(|r| {
r.tag_name == version
|| r.tag_name
.strip_prefix(&format!("{version}-"))
.is_some_and(|suffix| suffix.parse::<u32>().is_ok())
})
.max_by_key(|r| {
r.tag_name
.strip_prefix(&format!("{version}-"))
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(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.

medium

This function is a literal duplicate of the logic implemented in get_release_with_build_revision. Since mod tests is a child module, it can directly access private helper functions in the parent module. Refactoring the selection logic into a single shared function in the parent module would reduce code duplication and ensure that the tests are actually exercising the code used in production.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 4, 2026

Greptile Summary

This PR fixes mise.lock breakage when jdx/ruby republishes binaries under a build-revision tag (e.g. 3.3.11-1). It adds pick_best_build_revision / get_release_with_build_revision in github.rs and extract_build_revision_from_lock_platforms in ruby.rs to prefer the highest -N revision on first lock and then preserve it on subsequent runs.

Both prior review concerns are addressed: the test helper no longer duplicates production logic (tests call the extracted pick_best_build_revision directly), and the pagination limitation is now documented in the get_release_with_build_revision doc-comment.

Confidence Score: 5/5

Safe to merge — no P0/P1 issues found and both previous review concerns are resolved.

All remaining findings are P2 or lower. The shared pick_best_build_revision helper is well-tested (4 unit tests covering the key cases), the fallback chain is correct, and the documented pagination caveat is the same trade-off that existed before this PR for older versions.

No files require special attention.

Important Files Changed

Filename Overview
src/github.rs Adds get_release_with_build_revision (public async) and pick_best_build_revision (private sync helper); the prior comment about duplicated test logic is resolved — tests now call the shared pick_best_build_revision directly; pagination limitation for older versions is documented in the docstring.
src/plugins/core/ruby.rs Adds extract_build_revision_from_lock_platforms to parse existing lockfile URLs, then threads locked_build_revision through find_precompiled_asset_in_repo and resolve_precompiled_url; both install_precompiled and resolve_lock_info call-sites updated consistently.

Sequence Diagram

sequenceDiagram
    participant User as mise lock / install
    participant Ruby as RubyPlugin
    participant Lock as ToolVersion.lock_platforms
    participant GH as github::*

    User->>Ruby: resolve_lock_info / install_precompiled
    Ruby->>Lock: extract_build_revision_from_lock_platforms(tv, version)
    alt locked revision found (e.g. 3.3.11-1)
        Lock-->>Ruby: Some("3.3.11-1")
        Ruby->>GH: get_release(repo, "3.3.11-1")
        alt release exists
            GH-->>Ruby: GithubRelease { tag: "3.3.11-1", ... }
        else release deleted
            GH-->>Ruby: Err
            Ruby->>GH: get_release_with_build_revision(repo, "3.3.11")
            GH->>GH: list_releases → pick_best_build_revision
            GH-->>Ruby: GithubRelease { tag: "3.3.11-2", ... }
        end
    else no locked revision (first run or base tag locked)
        Lock-->>Ruby: None
        Ruby->>GH: get_release_with_build_revision(repo, "3.3.11")
        GH->>GH: list_releases → pick_best_build_revision
        GH-->>Ruby: GithubRelease { tag: "3.3.11-1", ... }
    end
    Ruby-->>User: url + checksum (locked to specific build revision)
Loading

Reviews (4): Last reviewed commit: "refactor(ruby): extract shared build rev..." | Re-trigger Greptile

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 9caa901. Configure here.

jdx and others added 3 commits April 4, 2026 16:17
When a build revision is already locked in the lockfile (extracted from
the stored URL), reuse that exact revision instead of always resolving
to the latest. Falls back to latest build revision if the locked one
is no longer available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…feedback

- Extract `pick_best_build_revision` as shared helper used by both
  production code and unit tests (no more duplicated logic)
- Pre-compute version prefix string outside filter/max_by_key closures
- Document pagination limitation in doc comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx changed the title fix(ruby): use latest build revision for precompiled binaries fix(ruby): support build revisions for precompiled binaries in mise.lock Apr 4, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 4, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 x -- echo 22.3 ± 0.4 21.5 25.9 1.00
mise x -- echo 23.0 ± 0.8 22.1 31.3 1.03 ± 0.04

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 env 22.3 ± 0.6 21.4 29.1 1.00
mise env 23.0 ± 0.7 21.7 27.3 1.03 ± 0.04

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 hook-env 22.9 ± 0.7 21.8 28.8 1.00
mise hook-env 23.4 ± 0.9 22.4 35.4 1.02 ± 0.05

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.3 ls 20.3 ± 0.6 19.1 22.5 1.00
mise ls 20.7 ± 0.5 19.7 22.3 1.02 ± 0.04

xtasks/test/perf

Command mise-2026.4.3 mise Variance
install (cached) 151ms 151ms +0%
ls (cached) 80ms 79ms +1%
bin-paths (cached) 84ms 83ms +1%
task-ls (cached) 2751ms ✅ 810ms +239%

✅ Performance improvement: task-ls cached is 239%

@jdx jdx enabled auto-merge (squash) April 4, 2026 16:52
@jdx jdx merged commit c2c4ec2 into main Apr 4, 2026
38 checks passed
@jdx jdx deleted the feat/build-revisions branch April 4, 2026 16:54
jdx pushed a commit that referenced this pull request Apr 5, 2026
### 🚀 Features

- **(ci)** auto-convert external PRs to draft mode by @jdx in
[#8896](#8896)
- **(deps)** add `depends` field for user-specified tool dependencies by
@cprecioso in [#8776](#8776)
- **(dotnet)** support runtime-only installs by @fragon10 in
[#8524](#8524)
- **(npm)** apply install_before to transitive dependencies by @risu729
in [#8851](#8851)
- **(task)** allow passing arguments to task dependencies via
{{usage.*}} templates by @jdx in
[#8893](#8893)
- add options field to BackendListVersionsCtx by @esteve in
[#8875](#8875)

### 🐛 Bug Fixes

- **(backend)** filter PEP 440 .dev versions in fuzzy version matching
by @richardthe3rd in [#8849](#8849)
- **(ci)** update COPR BuildRequires rust version to match MSRV 1.88 by
@jdx in [#8911](#8911)
- **(ci)** add Ruby build dependencies to e2e Docker image by @jdx in
[#8910](#8910)
- **(ci)** add missing build dependencies to e2e Docker image by @jdx in
[#8912](#8912)
- **(ci)** add missing build dependencies to e2e Docker image by @jdx in
[#8914](#8914)
- **(ci)** use Node 24 LTS for corepack e2e test by @jdx in
[#8915](#8915)
- **(ci)** add libxml2 and pkg-config to e2e Docker image by @jdx in
[#8917](#8917)
- **(ci)** add libxml2-dev to e2e image and disable Swift SPM tests by
@jdx in [#8918](#8918)
- **(docs)** use sans-serif font for badges by @jdx in
[#8887](#8887)
- **(env)** parse --env=VALUE and -E=VALUE flag forms correctly by @jdx
in [#8889](#8889)
- **(exec)** use i64::from() for seccomp syscall numbers to survive
autofix by @jdx in [#8882](#8882)
- **(github)** preserve tool options like filter_bins when version
specified via CLI by @jdx in
[#8888](#8888)
- **(github)** use alias-specific options when tool_alias has its own
config by @jdx in [#8892](#8892)
- **(install)** add locked_verify_provenance setting and detect github
attestations at lock time by @jdx in
[#8901](#8901)
- **(lock)** prune stale version entries during filtered `mise lock
<tool>` runs by @altendky in
[#8599](#8599)
- **(python)** use lockfile URL for precompiled installs by @hehaoqian
in [#8750](#8750)
- **(release)** verify all build targets succeed before releasing by
@jdx in [#8886](#8886)
- **(ruby)** support build revisions for precompiled binaries in
mise.lock by @jdx in [#8900](#8900)
- **(swift)** fall back to Ubuntu 24.04 for unsupported Ubuntu versions
by @jdx in [#8916](#8916)
- **(zsh)** avoid duplicate trust warning after cd by @timothysparg in
[#8898](#8898)
- update flake.lock and add fix for rust-bindgen to default.nix by
@esteve in [#8874](#8874)
- when direnv diff is empty, do not try to parse it by @yaleman in
[#8857](#8857)
- skip trust check for plain .tool-versions in task list by @dportalesr
in [#8876](#8876)

### 🚜 Refactor

- **(go)** rename go_* settings to go.* namespace by @jdbruijn in
[#8598](#8598)

### 📚 Documentation

- **(tasks)** clarify task_config.includes behavior by @risu729 in
[#8905](#8905)

### 🧪 Testing

- **(ci)** run e2e tests inside Docker containers by @jdx in
[#8899](#8899)

### 📦️ Dependency Updates

- bump ubi from 0.8 to 0.9 by @jdx in
[#8906](#8906)
- bump zip from 3 to 8 by @jdx in
[#8908](#8908)
- update lockfile deps (hold back rattler) by @jdx in
[#8909](#8909)
- update bun.lock by @jdx in
[#8913](#8913)

### 📦 Registry

- add turso
([github:tursodatabase/turso-cli](https://github.com/tursodatabase/turso-cli))
by @kenn in [#8884](#8884)
- remove carp test by @jdx in
[#8894](#8894)

### Chore

- **(ci)** add workflow to warn PRs modifying vendored aqua-registry by
@jdx in [#8897](#8897)
- **(ci)** use github.token for draft conversion in auto-draft workflow
by @jdx in [#8903](#8903)
- remove deprecated settings older than 12 months by @jdx in
[#8904](#8904)

### New Contributors

- @dportalesr made their first contribution in
[#8876](#8876)
- @timothysparg made their first contribution in
[#8898](#8898)
- @hehaoqian made their first contribution in
[#8750](#8750)
- @jdbruijn made their first contribution in
[#8598](#8598)
- @cprecioso made their first contribution in
[#8776](#8776)
- @yaleman made their first contribution in
[#8857](#8857)
- @kenn made their first contribution in
[#8884](#8884)
- @fragon10 made their first contribution in
[#8524](#8524)

## 📦 Aqua Registry Updates

#### New Packages (6)

- [`ahkohd/oyo`](https://github.com/ahkohd/oyo)
- [`bellicose100xp/jiq`](https://github.com/bellicose100xp/jiq)
- [`kurama/dealve-tui`](https://github.com/kurama/dealve-tui)
- [`micahkepe/jsongrep`](https://github.com/micahkepe/jsongrep)
- [`textfuel/lazyjira`](https://github.com/textfuel/lazyjira)
- [`ubugeeei/vize`](https://github.com/ubugeeei/vize)

#### Updated Packages (1)

- [`sigstore/cosign`](https://github.com/sigstore/cosign)
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