windmill-labs/windmill · error

Failed to download esbuild-wasm@${version} (${res.status} ${

Error message

Failed to download esbuild-wasm@${version} (${res.status} ${res.statusText}). Set WINDMILL_ESBUILD_WASM_PATH to an extracted esbuild-wasm package dir, point WINDMILL_ESBUILD_WASM_URL at a reachable tarball, or repair the native esbuild install.

What it means

This error is thrown by ensureWasmPackage in cli/src/utils/esbuild_loader.ts when the CLI's esbuild-wasm fallback cannot download the esbuild-wasm npm tarball. The fallback activates only after native esbuild fails its smoke test (e.g. host/binary version mismatch), so this error means the native esbuild is broken AND the wasm rescue download failed. The thrown message carries the HTTP status/statusText and the three configured escape hatches (WINDMILL_ESBUILD_WASM_PATH, WINDMILL_ESBUILD_WASM_URL, or repairing native esbuild).

Source

Thrown at cli/src/utils/esbuild_loader.ts:128

 * and extracts the npm tarball.
 */
async function ensureWasmPackage(version: string): Promise<string> {
  // Explicit local override wins (air-gapped / self-hosted workers): a path to
  // an already-extracted esbuild-wasm package directory.
  const override = process.env.WINDMILL_ESBUILD_WASM_PATH;
  if (override) return override;

  const destDir = path.join(cacheDir(), `esbuild-wasm-${version}`);
  if (fs.existsSync(path.join(destDir, "lib", "main.js"))) {
    return destDir;
  }

  const url = process.env.WINDMILL_ESBUILD_WASM_URL ??
    `https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-${version}.tgz`;
  log.info(`Downloading esbuild-wasm@${version} from ${url} ...`);
  const res = await fetch(url);
  if (!res.ok || !res.body) {
    throw new Error(
      `Failed to download esbuild-wasm@${version} (${res.status} ${res.statusText}). ` +
        `Set WINDMILL_ESBUILD_WASM_PATH to an extracted esbuild-wasm package dir, ` +
        `point WINDMILL_ESBUILD_WASM_URL at a reachable tarball, or repair the native esbuild install.`
    );
  }

  // Extract to a unique temp dir and rename into place so a crash or a
  // concurrent writer can't leave a half-extracted package behind, and so two
  // extractions never share an in-progress directory.
  const tmpDir = `${destDir}.${process.pid}.${extractCounter++}.tmp`;
  fs.rmSync(tmpDir, { recursive: true, force: true });
  await extractTarball(res.body, tmpDir);
  if (!fs.existsSync(path.join(tmpDir, "lib", "main.js"))) {
    fs.rmSync(tmpDir, { recursive: true, force: true });
    throw new Error(`esbuild-wasm@${version} tarball did not contain lib/main.js`);
  }
  try {
    fs.renameSync(tmpDir, destDir);

View on GitHub (pinned to e474e8803c)

Solutions

  1. Fix network access to the registry (check proxy/firewall/DNS; verify with `curl -I https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.28.0.tgz`).
  2. Set WINDMILL_ESBUILD_WASM_PATH to an already-extracted esbuild-wasm package directory containing lib/main.js (works fully offline).
  3. Set WINDMILL_ESBUILD_WASM_URL to a reachable tarball (internal mirror or a manually downloaded .tgz).
  4. Repair the native esbuild install (`npm rebuild esbuild` or reinstall the CLI) so the wasm fallback is never needed.

Example fix

// before: broken native install + no network path
// CLI falls back to wasm download and fails
// after: provide an offline override
export WINDMILL_ESBUILD_WASM_PATH=/opt/vendor/esbuild-wasm-0.28.0
# dir must contain lib/main.js, bin/esbuild, esbuild.wasm
Defensive patterns

Strategy: fallback

Validate before calling

// Check the fallback source is reachable before running wmill
const url = process.env.WINDMILL_ESBUILD_WASM_URL ??
  `https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.28.0.tgz`;
const res = await fetch(url, { method: "HEAD" });
if (!res.ok) throw new Error(`esbuild-wasm source unreachable: ${res.status} — set WINDMILL_ESBUILD_WASM_PATH or fix network`);
// or, for offline environments, verify the override:
// fs.existsSync(path.join(process.env.WINDMILL_ESBUILD_WASM_PATH, "lib", "main.js"))

Type guard

function isOkResponse(res: Response): boolean {
  return res.ok && res.body !== null;
}

Try / catch

try {
  await getEsbuild();
} catch (e) {
  if (String(e).includes("Failed to download esbuild-wasm")) {
    // repair native esbuild so the wasm fallback is not needed
    console.error("Fix network or set WINDMILL_ESBUILD_WASM_PATH to an extracted esbuild-wasm dir");
  }
  throw e;
}

Prevention

When it happens

Trigger: Native esbuild fails its transform() smoke test, so the code fetches the tarball from WINDMILL_ESBUILD_WASM_URL or https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-<version>.tgz, and fetch returns res.ok === false or an empty body (HTTP 404, 403, 5xx, or a network error page).

Common situations: Air-gapped or firewalled machines/CI where registry.npmjs.org is unreachable; a corporate proxy blocking the fetch; a custom WINDMILL_ESBUILD_WASM_URL pointing at a missing or wrong-version tarball; a broken npm install (version-mismatched @esbuild/<platform> binary) that triggered the fallback in the first place; npm registry outages or rate limiting.

Related errors


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