Skip to content

fix(core): warn loudly when falling back to the WebAssembly runtime - #36596

Draft
llwt wants to merge 3 commits into
masterfrom
wasm-fallback-warning
Draft

fix(core): warn loudly when falling back to the WebAssembly runtime#36596
llwt wants to merge 3 commits into
masterfrom
wasm-fallback-warning

Conversation

@llwt

@llwt llwt commented Aug 7, 2026

Copy link
Copy Markdown
Member

Current Behavior

When the platform-native binary is missing from node_modules, nx silently falls back to its bundled WebAssembly runtime:

  • native-bindings.js collects native load errors into an array and quietly loads nx.wasi.cjs, which ships inside the nx package, so the fallback always succeeds.
  • The only user-facing WASM warning lives in the daemon client's enabled check. In CI the daemon is disabled by an earlier branch (isCI() || isDocker(), or NX_DAEMON=false), so the warning cannot fire in exactly the environment where this failure mode occurs.
  • src/native/index.js suppresses node's own WASI ExperimentalWarning.
  • Once synchronous WASM execution starts, the event loop blocks and queued async console output to a piped stdout/stderr is never delivered.

The common trigger is a lockfile that lost its platform optionalDependencies (regenerated on a different OS, npm/cli#4828, or a merge conflict). Real-world impact: an enterprise customer's package-lock.json contained only @nx/nx-darwin-arm64, so npm ci on Linux runners installed no native binding. nx-cloud start-ci-run spun at 100% CPU for up to 2.5 hours per job with zero output even under NX_VERBOSE_LOGGING=true, and the diagnosis took several days because nothing pointed at the install.

Expected Behavior

WASM is only treated as a fallback when the expected native package is unresolvable, which is the broken-install signature a pruned lockfile produces. Under that predicate, nx prints one loud, actionable warning to stderr naming the exact missing package and the fix:

  • New dependency-free helper src/native/wasm-fallback-warning.js (pure and unit-tested) computes the message from platform/arch/libc.
  • The warning is gated on the native package being unresolvable (MODULE_NOT_FOUND), not on platform inference. Environments where the package is installed but cannot be loaded, such as WebContainers/StackBlitz (which report linux/x64 yet cannot dlopen native addons), stay quiet, as do platforms without prebuilt natives, NAPI_RS_FORCE_WASI, and NX_ALLOW_WASM_FALLBACK=true.
  • src/native/index.js emits it with a synchronous fs.writeSync to stderr. A sync write is load-bearing here: async output is lost once the process blocks in synchronous WASM execution, which is why the customer saw nothing.
  • The warning fires once per process tree (NX_WASM_FALLBACK_WARNED env var dedupe), since nx spawns many child processes.
  • nx report now includes a neutral Native runtime field (native or wasm), appending (missing @nx/nx-<platform>) only under the same unresolvable-package predicate, so both surfaces share one definition of "broken install".
  • The existing WASI ExperimentalWarning suppression is intentionally kept: one clear deduped warning is more useful than N cryptic node warnings.
  • project.json output globs were adjusted so the new checked-in helper is not matched by the napi build's output glob and clobbered by cache restores.

Proposed follow-up for discussion (input wanted)

  1. Should this go further and hard-fail in CI? Concretely: when the unresolvable-package predicate holds and isCI() is true, throw an actionable error unless NX_ALLOW_WASM_FALLBACK=true is set. Rationale: a missing platform native in CI is almost always a broken install, and failing in seconds with the package name beats a multi-hour silent hang. Open questions: is warn-locally/error-in-CI the right split, and is NX_ALLOW_WASM_FALLBACK the right escape-hatch name?
  2. The package-resolves-but-fails-to-load case (glibc mismatch, unsupported dlopen) deliberately stays quiet in this PR to avoid false positives in WebContainers-style environments. A targeted hint for glibc mismatches could be useful later; input welcome on whether that is worth distinguishing.

Related Issue(s)

N/A (from an enterprise support incident; context in the linked Polygraph session)

When the platform native binary is missing from node_modules, the
generated native-bindings.js silently falls back to the bundled WASM
runtime. That fallback always succeeds, so nx keeps running but the
workspace context and hasher can spin at 100% CPU for hours with no
output, which reads as a hang rather than a broken install.

The one existing WASM warning lives behind the daemon-enabled check, and
in CI the isCI/isDocker branch above it wins first, so it never fires in
the environment where this actually bites.

Warn on stderr as soon as IS_WASM is observed, naming the missing
platform package and the lockfile cause behind it. The warning uses a
synchronous fd write because a queued async write cannot drain once the
process blocks in synchronous WASM work, and dedupes through
NX_WASM_FALLBACK_WARNED so the processes nx spawns do not each repeat
it. NX_ALLOW_WASM_FALLBACK=true silences it, and genuine WASM-only
platforms stay quiet.

Also surface the runtime in nx report so the fallback is visible when
diagnosing after the fact.
@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for nx-docs failed. Why did it fail? →

Name Link
🔨 Latest commit 94ac920
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6a7539716c5ede0008e43528

@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 94ac920
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6a7539714572250008e285f0
😎 Deploy Preview https://deploy-preview-36596--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud

nx-cloud Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 94ac920

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ⛔ Cancelled 1h 14m 30s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 4s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 54s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 17s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 4s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-07 03:07:22 UTC

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud Bot and others added 2 commits August 7, 2026 01:25
Co-authored-by: llwt <llwt@users.noreply.github.com>
…ackage is unresolvable

Gating the warning on platform and arch alone produces a false positive.
WebContainers and StackBlitz report linux/x64 and install
@nx/nx-linux-x64-gnu normally, but dlopen fails there, so nx correctly
runs WASM. Every StackBlitz boot printed a broken lockfile diagnosis
that did not apply, and nx report called the runtime a fallback for the
same reason.

Gate both surfaces on the signature of the install this actually
targets: the expected native package cannot be resolved at all. A
package that resolves but will not load is left alone, as is
NAPI_RS_FORCE_WASI and any platform outside the prebuilt matrix. Only a
MODULE_NOT_FOUND counts as absent, so any other resolution error is
treated as present and stays quiet.

The expected package name and the resolvability probe are shared
between the startup warning and nx report so the two cannot disagree.
The report field is now a neutral native or wasm, and only appends
(missing <package>) under that same predicate.

@nx-cloud nx-cloud Bot left a comment

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.

Nx Cloud is proposing a fix for your failed CI:

We formatted astro-docs/src/content/docs/reference/environment-variables.mdoc to fix the astro-docs:format prettier check failure. The two new table rows added by this PR (NX_ALLOW_WASM_FALLBACK and NX_WASM_FALLBACK_WARNED) had descriptions exceeding the existing column width, causing prettier to reject the misaligned table. Running prettier --write on the file expanded the column separators to match the widest cell, bringing the file back into compliance.

Warning

  • We could not verify this fix.
  • The suggested diff is too large to display here, but you can view it on Nx Cloud ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally GMuW-AyST

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant