Skip to content

fix: fall back to musl binary when gnu prebuild fails to load#9

Merged
dsanders11 merged 5 commits into
mainfrom
fix/musl-loader-fallback
Jun 23, 2026
Merged

fix: fall back to musl binary when gnu prebuild fails to load#9
dsanders11 merged 5 commits into
mainfrom
fix/musl-loader-fallback

Conversation

@claude

@claude claude Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Requested by Samuel Attard · Slack thread

Before: on a glibc (gnu) Linux box where the gnu prebuild can't load — for example because the system glibc is older than what the prebuild was linked against, so require('./index.linux-x64-gnu.node') throws GLIBC_2.33 not found — install hard-fails, even though we already ship a working musl binary in the same package.

After: when the gnu prebuild fails to load, the loader falls back to the bundled index.linux-x64-musl.node / index.linux-arm64-musl.node. The musl binary is statically linked, so it runs fine on glibc hosts. A usable binary that's already in the package is no longer unreachable.

How: the napi-rs-generated binding.js picks gnu vs musl purely by libc family (isMusl()), with no glibc-version check and no cross-family fallback — so on a glibc host it only ever tries the gnu binary and gives up when that throws. Rather than hand-editing generated output (which gets clobbered on the next build), this change lives in scripts/strip-binding-fallbacks.js, the post-processor that already rewrites the generated loader. For the linux gnu x64 and arm64 branches it injects a fallback: after the gnu .node require fails it also tries the corresponding musl .node, wrapped in its own try/catch that pushes to loadErrors on failure (matching the existing generated style). It can only ever turn a hard install failure into a working load, and never changes behaviour when the gnu binary loads.

This is a defense-in-depth safety net and is one of two PRs. The sibling PR fixes the build toolchain so the gnu prebuild targets an old-enough glibc in the first place; this one ensures a too-new gnu build can never hard-fail when a usable musl binary is present in the package.

Refs electron/electron#52099, electron/electron#52101.

claude and others added 5 commits June 23, 2026 21:45
Before: the napi-rs-generated loader selects between the gnu and musl
binaries purely by libc *family* (`isMusl()`), with no glibc-version
check and no cross-family fallback. On a glibc (gnu) Linux host whose
system glibc is older than what the gnu prebuild was linked against,
`require('./index.linux-<arch>-gnu.node')` throws (e.g.
`GLIBC_2.33 not found`); the loader records the error and gives up,
hard-failing install even though we also ship a statically-linked musl
binary that runs fine on glibc hosts.

After: the binding post-processor injects a fallback into the linux
gnu (x64 and arm64) branches so that, when the gnu `.node` require
fails, the loader also tries the bundled
`./index.linux-<arch>-musl.node` before giving up. The musl require is
wrapped in its own try/catch and pushes to `loadErrors` on failure,
matching the existing generated style.

The change lives in scripts/strip-binding-fallbacks.js (which already
post-processes the generated binding.js) so it survives regeneration,
rather than being a hand-edit of generated output that would be
clobbered on the next build.
The gnu->musl fallback injection in strip-binding-fallbacks.js required the
linux-<arch>-gnu require block to be present and process.exit(1)'d otherwise.
Per-target musl builds (napi build --target *-musl) emit a loader with no gnu
require block, so the guard tripped and failed the linux-x64-musl /
linux-arm64-musl CI jobs.

Make the injection tolerant: if an arch's gnu block is found, inject the musl
fallback as before; if it's absent, skip that arch and continue. The fallback
is only meaningful when a gnu block exists, so skipping is correct. The Step 2
package-name fallback strip is unchanged.
This restores the script to its pre-#9 behavior: it only strips the
@electron-internal/* package-name fallbacks from the generated binding.js.
The gnu->musl fallback is being moved to a @napi-rs/cli patch (mirroring
how #7 added the ia32 branch), so the runtime string-rewrite injection is
no longer needed here.
Mirror PR #7's approach (which added the linux-ia32-gnu branch by patching
the napi-rs loader template) and add a gnu->musl fallback the same way,
instead of the runtime regex string-rewrite that was in
strip-binding-fallbacks.js.

The generated loader picks gnu vs musl purely by libc *family* (isMusl()),
with no glibc-version check. On a glibc host whose system glibc is older
than what our gnu prebuild was linked against, requiring
./index.linux-<arch>-gnu.node throws (e.g. GLIBC_2.33 not found) and the
loader gives up — even though we also bundle a statically-linked musl
binary that runs fine on glibc hosts.

This patches @napi-rs/cli's requireNative() template so the linux x64 and
arm64 gnu (else) branches also try ./index.linux-<arch>-musl.node after the
gnu require fails, using the generator's own requireTuple() helper so the
emitted code matches the generated style exactly. Because the fallback is
baked into the template at install time, the generated binding.js comes out
correct on every build regardless of how many times the build runs —
avoiding the docker+host double-pass idempotency problem that affected the
regex post-processor.
Regenerated by `yarn install --mode update-lockfile` after editing the
@napi-rs/cli patch. Only the patched-package resolution hash and checksum
change (the patch now also adds the gnu->musl fallback). Keeps
`yarn install --immutable` green in CI.
@MarshallOfSound MarshallOfSound marked this pull request as ready for review June 23, 2026 22:14
@MarshallOfSound MarshallOfSound requested a review from a team as a code owner June 23, 2026 22:14

@dsanders11 dsanders11 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking on adding validation in CI.

@dsanders11 dsanders11 merged commit 3eb9258 into main Jun 23, 2026
18 checks passed
@dsanders11 dsanders11 deleted the fix/musl-loader-fallback branch June 23, 2026 22:39
@electron-npm-package-publisher

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants