windmill-labs/windmill · warning
native esbuild is not usable; falling back to esbuild-wasm (
Error message
native esbuild is not usable; falling back to esbuild-wasm (${msg.trim()}) What it means
acquireEsbuild loads the native esbuild package and runs a smoke test (esbuild.transform("")) because the native service only starts on first use — a broken install (e.g. host/binary version mismatch after a partial npm install, or a dead service) only surfaces then, and the thrown error is generic. On ANY smoke-test failure it logs this warning with the underlying message and falls back to esbuild-wasm, downloading and caching it if needed. The CLI still works, just slower.
Source
Thrown at cli/src/utils/esbuild_loader.ts:73
async function acquireEsbuild(): Promise<Esbuild> {
// Escape hatch: skip native entirely (e.g. a host known to have a broken
// install, or to exercise the fallback path).
if (process.env.WINDMILL_FORCE_ESBUILD_WASM) {
return loadWasmEsbuild(await nativeHostVersion());
}
try {
const esbuild = await import("esbuild");
// The native service only starts on the first call; force it with the most
// trivial op so any breakage (host/binary version mismatch, a dead service)
// surfaces now rather than mid-build. The mismatch detail is printed to the
// child's stderr while the thrown error is generic ("service was stopped"),
// so we fall back on ANY smoke-test failure rather than matching a string.
await esbuild.transform("");
return esbuild;
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
log.warn(
`native esbuild is not usable; falling back to esbuild-wasm (${msg.trim()})`
);
}
return loadWasmEsbuild(await nativeHostVersion());
}
/**
* Stops the esbuild service (native or wasm — both spawn a child process) so the
* process can exit. Safe to call repeatedly; the service restarts lazily on the
* next build.
*/
export async function stopEsbuild(): Promise<void> {
await cached?.stop();
}
async function nativeHostVersion(): Promise<string> {
try {View on GitHub (pinned to e474e8803c)
Solutions
- Reinstall dependencies cleanly: rm -rf node_modules package-lock.json && npm install (or bun install) to realign esbuild host and native binary versions.
- Verify the native binary: npx esbuild --version — if it errors, the install is broken.
- If the platform genuinely lacks native esbuild, accept the wasm fallback; set WINDMILL_ESBUILD_WASM_PATH to a pre-extracted esbuild-wasm dir for air-gapped machines.
- Set WINDMILL_FORCE_ESBUILD_WASM=1 to intentionally skip native and skip the warning path's failed probe.
- Check the CLI package integrity (reinstall wmill) if only its bundled esbuild is broken.
Example fix
// before wmill app build ... // native esbuild is not usable; falling back to esbuild-wasm (Cannot start service: Host version ...) // after: repair the install rm -rf node_modules npm ci wmill app build ... # no fallback warning
Defensive patterns
Strategy: fallback
Validate before calling
try {
const esbuild = await import("esbuild");
await esbuild.transform("");
console.log("native esbuild OK");
} catch (e) {
console.log("native esbuild broken — reinstall deps or pre-seed WINDMILL_ESBUILD_WASM_PATH:", e);
} Try / catch
try {
await $`wmill app build ...`;
} catch (e) {
// fallback usually succeeds; only fail on real errors, e.g. wasm download failure
if (String(e).includes("Failed to download esbuild-wasm")) {
process.env.WINDMILL_ESBUILD_WASM_PATH = "/opt/vendored/esbuild-wasm";
await $`wmill app build ...`;
} else throw e;
} Prevention
- Use npm ci / lockfiles so esbuild host and @esbuild/<platform> binary versions always match
- Set WINDMILL_ESBUILD_WASM_PATH (or WINDMILL_ESBUILD_WASM_URL) in air-gapped environments so the fallback needs no download
- Verify installs with `npx esbuild --version` in CI setup
- Set WINDMILL_FORCE_ESBUILD_WASM=1 deliberately on platforms known to break native esbuild
- Check that endpoint protection isn't quarantining the native esbuild binary
When it happens
Trigger: First esbuild use in the CLI (app/dependency builds) when the native esbuild import or its smoke-test transform throws: @esbuild/<platform> binary missing or version-mismatched with the JS host, corrupted node_modules, unsupported platform, or the spawned esbuild service dying immediately.
Common situations: Incremental/partial `npm install` leaving esbuild host at a different version than the native binary ("Cannot start service: Host version X does not match binary version Y"); running the CLI on an unusual platform where the binary wasn't installed; AV software deleting the native binary; followed by a 14MB esbuild-wasm download on first fallback.
Related errors
- Failed to download esbuild-wasm@${version} (${res.status} ${
- No output files found for ${filePath}
- Local PathScript ${filePath} requires a multi-file bundle, w
- Local PathScript ${filePath} requires codebase assets, which
- Failed to resolve local relative imports for preview: ${msg}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/9de040ef99c23528.
Report an issue: GitHub.