Skip to content

fix(node): route musl tarball URLs to unofficial-builds#9409

Merged
jdx merged 2 commits intomainfrom
claude/musing-nobel-f2c456
Apr 26, 2026
Merged

fix(node): route musl tarball URLs to unofficial-builds#9409
jdx merged 2 commits intomainfrom
claude/musing-nobel-f2c456

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 26, 2026

Summary

PR #9404 (feat(backend): add global libc preference) taught Node's slug builder to append -musl for musl targets but kept routing through nodejs.org/dist/, which does not host musl tarballs (they live at unofficial-builds.nodejs.org). The visible symptom was in PR #9396's mise.lock diff: the URL gained a -musl suffix while the checksum stayed pinned to the original glibc tarball.

[tools.node."platforms.linux-x64-musl"]
checksum = "sha256:dbf5b8665..."   # ← still the glibc checksum
url = ".../v24.14.0/node-v24.14.0-linux-x64-musl.tar.gz"   # ← 404, wrong host

Mechanically: resolve_lock_info builds a 404'ing URL on nodejs.org/dist, fetches the wrong SHASUMS256.txt (which doesn't list -musl.tar.gz), gets None back, and the lockfile merge preserves the stale glibc checksum alongside the new URL. Anyone running mise install against that lockfile on a musl system would either 404 or hit a checksum mismatch.

The aqua/github-backed tools in the same release diff updated cleanly because their checksum source rotates with the artifact. Node is unique in fetching checksums from a separate SHASUMS256.txt.

Changes

src/plugins/core/node.rs

Add mirror_url_for(&SettingsNode, filename) that swaps to https://unofficial-builds.nodejs.org/download/release/ when a filename references a musl artifact and the user has not set a custom node.mirror_url. Wire it into resolve_lock_info, get_tarball_url, BuildOpts::new, and shasums_url so the tarball URL and the matching SHASUMS256.txt always come from the same host. Three unit tests cover routing (default → glibc, musl → unofficial-builds, custom mirror passes through unchanged).

src/lockfile.rs

Defense in depth: when merging PlatformInfo (both in set_platform_info and merge_with), drop the other side's checksum/size/url_api if URLs disagree — those fields describe a specific artifact and become stale once the URL changes. The pre-existing test_platform_info_merge_prefers_sha256 was asserting that sha256 should win even across mismatched URLs, which is exactly the latent bug; updated it to use a shared URL and added test_platform_info_merge_drops_stale_checksum_on_url_change.

mise.lock

Re-ran mise lock node to fix the three corrupted node musl entries. Checksums verified against upstream:

$ curl -fsSL https://unofficial-builds.nodejs.org/download/release/v24.14.0/SHASUMS256.txt | grep -E "linux-(arm64|x64)-musl\.tar\.gz"
8f81d47b7f...  node-v24.14.0-linux-arm64-musl.tar.gz
bae0f23204...  node-v24.14.0-linux-x64-musl.tar.gz

Both match what mise now writes.

Test plan

  • cargo test — 778 passed, 0 failed (includes 3 new node tests + 1 new lockfile test, plus an updated test that previously asserted the latent bug).
  • cargo clippy --all-features --tests — clean.
  • cargo fmt — clean.
  • mise lock node against this branch produces correct URLs + checksums for all three node musl platform variants.
  • Install on Alpine / musl host: MISE_LIBC=musl mise install node@24.14.0 should download from unofficial-builds.nodejs.org and run.
  • Glibc regression: same flow without MISE_LIBC=musl should still hit nodejs.org/dist.

This PR was generated by an AI coding assistant.


Note

Medium Risk
Changes Node.js download/checksum URL selection and lockfile merge behavior, which can affect installs and lockfile correctness across platforms; scope is contained with added tests.

Overview
Fixes Node.js musl installs/lock resolution by routing musl tarball URLs (and their matching SHASUMS256.txt/signature URLs) to unofficial-builds.nodejs.org when using the default mirror, while still respecting user-configured node.mirror_url.

Hardens lockfile merging so when a platform artifact url changes, stale artifact-bound fields (checksum, size, url_api) from the other side are dropped instead of preserved, preventing mismatched URL+checksum pairs.

Regenerates mise.lock Node musl entries to use the unofficial-builds URLs with updated sha256 checksums, and adds/updates unit tests covering the new mirror routing and merge semantics.

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

PR #9404 taught Node's slug builder to append `-musl` for musl targets
but kept routing through `nodejs.org/dist/`, which does not host musl
tarballs (they live at unofficial-builds.nodejs.org). The result in PR
#9396's mise.lock was a `-musl.tar.gz` URL paired with the original
glibc checksum: `resolve_lock_info` produced a 404'ing URL and a `None`
checksum, and the lockfile merge preserved the stale glibc checksum
alongside the new URL.

Add a `mirror_url_for(&SettingsNode, filename)` helper that swaps the
mirror to `https://unofficial-builds.nodejs.org/download/release/` when
a filename references a musl artifact and the user has not set a custom
`node.mirror_url`. Wire it into `resolve_lock_info`, `get_tarball_url`,
`BuildOpts::new`, and `shasums_url` so the tarball URL and the matching
`SHASUMS256.txt` always come from the same host.

Defense in depth in `lockfile.rs`: when merging `PlatformInfo`, drop the
other side's checksum/size/url_api if URLs disagree — those fields
describe a specific artifact and become stale once the URL changes.

Regenerate the three broken `mise.lock` node musl entries; checksums
verified against upstream SHASUMS256.txt.

Co-Authored-By: Claude Opus 4.7 (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 updates the Node.js plugin to automatically route musl-based builds to the unofficial-builds mirror and modifies the lockfile merging logic to discard stale artifact metadata, such as checksums and sizes, when a URL changes. Feedback suggests refining the mirror_url_for implementation to use the mirror_url() method, which ensures that environment variables and other configuration sources are correctly prioritized before falling back to the unofficial mirror.

Comment thread src/plugins/core/node.rs
Comment on lines +859 to +864
fn mirror_url_for(node: &crate::config::settings::SettingsNode, filename: &str) -> Url {
if node.mirror_url.is_none() && filename.contains("-musl") {
return Url::parse(UNOFFICIAL_NODE_MIRROR_URL).unwrap();
}
node.mirror_url()
}
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 of mirror_url_for only checks the node.mirror_url configuration field. Following the principle of using provided parameters and their methods (like node.mirror_url()) ensures that environment variables and tool-specific configurations are correctly respected without re-deriving them from a general context.

Suggested change
fn mirror_url_for(node: &crate::config::settings::SettingsNode, filename: &str) -> Url {
if node.mirror_url.is_none() && filename.contains("-musl") {
return Url::parse(UNOFFICIAL_NODE_MIRROR_URL).unwrap();
}
node.mirror_url()
}
fn mirror_url_for(node: &crate::config::settings::SettingsNode, filename: &str) -> Url {
let mirror = node.mirror_url();
if filename.contains("-musl") && mirror.as_str() == DEFAULT_NODE_MIRROR_URL {
return Url::parse(UNOFFICIAL_NODE_MIRROR_URL).unwrap();
}
mirror
}
References
  1. When using provided parameters, use their methods to access values instead of re-deriving them from a more general context to ensure specific configurations are preserved.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

Fixes Node.js musl installs by routing -musl tarball URLs and their matching SHASUMS256.txt to unofficial-builds.nodejs.org via a new mirror_url_for helper, and hardens lockfile merging to drop stale artifact-bound fields (checksum, size, url_api) when a platform entry's URL changes. The mise.lock musl entries are regenerated with correct URLs and checksums.

Confidence Score: 4/5

Safe to merge; the core bug is fixed and well-tested, with one pre-existing incomplete fix for the baseline musl variant.

All three changed call-sites correctly use mirror_url_for and the lockfile merge guard is sound and covered by tests. The one open issue — build_platform_slug silently drops the -baseline suffix so linux-x64-musl-baseline resolves to the non-baseline musl artifact — is a pre-existing slug bug rather than a regression, and the PR strictly improves that case (musl binary instead of a wrong glibc binary). Score capped at 4 due to the P2 finding.

src/plugins/core/node.rsbuild_platform_slug at line 777 needs the baseline qualifier preserved for linux-x64-musl-baseline targets.

Important Files Changed

Filename Overview
src/plugins/core/node.rs Adds mirror_url_for to route -musl filenames to unofficial-builds.nodejs.org; wires it into resolve_lock_info, get_tarball_url, BuildOpts, and shasums_url. linux-x64-musl-baseline still uses the non-baseline musl slug because build_platform_slug drops the qualifier suffix.
src/lockfile.rs Adds URL-disagreement guard in both set_platform_info and merge_with to drop stale checksum/size/url_api when the artifact URL changes. Test updated and a new test covers the stale-checksum-on-URL-change scenario.
mise.lock Three Node musl platform entries regenerated: URLs now point to unofficial-builds.nodejs.org and checksums match the actual musl artifacts.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[resolve_lock_info / get_tarball_url / BuildOpts] --> B[build filename]
    B --> C{mirror_url_for}
    C -->|filename contains '-musl' AND mirror == default| D[unofficial-builds.nodejs.org]
    C -->|glibc OR custom mirror set| E[nodejs.org/dist OR custom mirror]
    D --> F[fetch SHASUMS256.txt from unofficial-builds]
    E --> G[fetch SHASUMS256.txt from chosen mirror]
    F --> H[PlatformInfo: url + checksum from same host]
    G --> H
    H --> I{set_platform_info / merge_with}
    I -->|new URL == existing URL| J[merge checksums, prefer sha256]
    I -->|new URL != existing URL| K[drop stale checksum/size/url_api]
    J --> L[lockfile updated]
    K --> L
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "fix(node): compare resolved mirror_url t..." | Re-trigger Greptile

The previous check `node.mirror_url.is_none()` ignored the
`NODE_BUILD_MIRROR_URL` env var that `mirror_url()` itself reads as a
fallback, so a user with that env var set would still get rerouted to
unofficial-builds for musl filenames. Compare against the resolved Url
instead so any user-provided override (settings field or env var) is
preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Owner Author

jdx commented Apr 26, 2026

Re: greptile's P2 finding on linux-x64-musl-baseline — checked upstream and there is no musl-baseline artifact:

$ curl -fsSL https://unofficial-builds.nodejs.org/download/release/index.tab \
    | awk -F'\t' '{print $3}' | tr ',' '\n' | sort -u | grep -E "baseline|musl"
linux-arm64-musl
linux-x64-musl

Across the entire history of unofficial-builds.nodejs.org, only linux-arm64-musl and linux-x64-musl have ever been published — no baseline variant exists for any version, including v24.14.0. The greptile suggestion would point the lockfile at node-v24.14.0-linux-x64-musl-baseline.tar.gz, which 404s.

The current behavior (linux-x64-musl-baseline slug collapses to linux-x64-musl) is the correct fallback given that upstream doesn't ship a separate baseline build for musl. Not addressing this finding.

This comment was generated by an AI coding assistant.

@jdx jdx merged commit bec289e into main Apr 26, 2026
36 checks passed
@jdx jdx deleted the claude/musing-nobel-f2c456 branch April 26, 2026 19:14
@github-actions
Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 x -- echo 23.5 ± 0.2 23.1 24.9 1.00
mise x -- echo 24.1 ± 0.3 23.5 25.7 1.02 ± 0.01

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 env 23.2 ± 1.0 22.5 38.8 1.00
mise env 23.9 ± 1.0 22.9 37.9 1.03 ± 0.06

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 hook-env 24.0 ± 0.6 23.1 30.2 1.00
mise hook-env 24.7 ± 0.6 23.5 26.5 1.03 ± 0.04

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.22 ls 24.0 ± 0.3 23.4 26.5 1.00
mise ls 24.9 ± 0.3 24.3 26.4 1.04 ± 0.02

xtasks/test/perf

Command mise-2026.4.22 mise Variance
install (cached) 169ms 176ms -3%
ls (cached) 81ms 84ms -3%
bin-paths (cached) 83ms 85ms -2%
task-ls (cached) 786ms 781ms +0%

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)
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