Node "@swc/core platform binary missing" in CI
@swc/core loads a native binding from a platform-specific package (e.g. @swc/core-linux-x64-gnu). When that optional package is missing, swc fails with "Failed to load native binding".
What this error means
A build or test using swc fails with "Failed to load native binding" or "Cannot find module '@swc/core-linux-x64-gnu'". It runs locally where the matching platform package was installed.
Error: Failed to load native binding
at Object.<anonymous> (/work/repo/node_modules/@swc/core/binding.js:1:1)
Cannot find module '@swc/core-linux-x64-gnu'Diagnose it: reproduce the CI install locally
Install failures are usually environment drift rather than a broken lockfile: a different package-manager major, a different Node version, or a cache that is being restored from a run with different inputs. Reproduce the CI conditions before changing the lockfile, because regenerating it hides the real cause.
# match the runner exactly, then install from a clean slate
node --version && npm --version
rm -rf node_modules
npm ci --foreground-scripts
# if that succeeds locally but fails in CI, the difference is the cache
# or the package-manager version, not your lockfileCommon causes
Optional platform package not installed
The @swc/core-<platform> optional dependency was skipped, so no native binding loads.
libc mismatch (gnu vs musl)
On Alpine, swc needs the -musl variant; the -gnu package present cannot load.
How to fix it
Install optional deps for the right platform
Include optional dependencies so the correct @swc/core-<platform> package installs.
npm ci --include=optionalMatch the libc variant to the image
On Alpine use a base/runner that pulls the -musl binding, or switch to a glibc image.
- Use a glibc image for the -gnu binding, or Alpine for -musl.
- Ensure the lockfile records the platform you build on.
- Reinstall after changing the base image.
Verify the fix survives a cold cache
A green run immediately after a fix often proves nothing, because it restored a cache written before the change. Force a cold install once to confirm the fix is real.
# temporarily bust the cache key to prove the fix on a cold runner
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: package-lock.json
# then bump this suffix once, run, and remove it
# key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}-v2How to prevent it
- Keep optional dependencies enabled so platform bindings install.
- Match the libc variant (gnu/musl) to the base image.
- Latchkey self-healing managed runners auto-retry transient native-binding download failures and cache platform packages so swc loads cleanly.