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

  1. Reinstall dependencies cleanly: rm -rf node_modules package-lock.json && npm install (or bun install) to realign esbuild host and native binary versions.
  2. Verify the native binary: npx esbuild --version — if it errors, the install is broken.
  3. 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.
  4. Set WINDMILL_FORCE_ESBUILD_WASM=1 to intentionally skip native and skip the warning path's failed probe.
  5. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/9de040ef99c23528. Report an issue: GitHub.