fix(node): route musl tarball URLs to unofficial-builds#9409
Conversation
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>
There was a problem hiding this comment.
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.
| 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() | ||
| } |
There was a problem hiding this comment.
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.
| 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
- 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 SummaryFixes Node.js musl installs by routing Confidence Score: 4/5Safe 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
Important Files Changed
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
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>
|
Re: greptile's P2 finding on $ 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-muslAcross the entire history of unofficial-builds.nodejs.org, only The current behavior ( This comment was generated by an AI coding assistant. |
Hyperfine Performance
|
| 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% |
### 🚀 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)
Summary
PR #9404 (
feat(backend): add global libc preference) taught Node's slug builder to append-muslfor musl targets but kept routing throughnodejs.org/dist/, which does not host musl tarballs (they live atunofficial-builds.nodejs.org). The visible symptom was in PR #9396'smise.lockdiff: the URL gained a-muslsuffix while the checksum stayed pinned to the original glibc tarball.Mechanically:
resolve_lock_infobuilds a 404'ing URL onnodejs.org/dist, fetches the wrongSHASUMS256.txt(which doesn't list-musl.tar.gz), getsNoneback, and the lockfile merge preserves the stale glibc checksum alongside the new URL. Anyone runningmise installagainst 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.rsAdd
mirror_url_for(&SettingsNode, filename)that swaps tohttps://unofficial-builds.nodejs.org/download/release/when a filename references a musl artifact and the user has not set a customnode.mirror_url. Wire it intoresolve_lock_info,get_tarball_url,BuildOpts::new, andshasums_urlso the tarball URL and the matchingSHASUMS256.txtalways come from the same host. Three unit tests cover routing (default → glibc, musl → unofficial-builds, custom mirror passes through unchanged).src/lockfile.rsDefense in depth: when merging
PlatformInfo(both inset_platform_infoandmerge_with), drop the other side'schecksum/size/url_apiif URLs disagree — those fields describe a specific artifact and become stale once the URL changes. The pre-existingtest_platform_info_merge_prefers_sha256was asserting that sha256 should win even across mismatched URLs, which is exactly the latent bug; updated it to use a shared URL and addedtest_platform_info_merge_drops_stale_checksum_on_url_change.mise.lockRe-ran
mise lock nodeto fix the three corrupted node musl entries. Checksums verified against upstream: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 nodeagainst this branch produces correct URLs + checksums for all three node musl platform variants.MISE_LIBC=musl mise install node@24.14.0should download fromunofficial-builds.nodejs.organd run.MISE_LIBC=muslshould still hitnodejs.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) tounofficial-builds.nodejs.orgwhen using the default mirror, while still respecting user-configurednode.mirror_url.Hardens lockfile merging so when a platform artifact
urlchanges, stale artifact-bound fields (checksum,size,url_api) from the other side are dropped instead of preserved, preventing mismatched URL+checksum pairs.Regenerates
mise.lockNode 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.