windmill-labs/windmill · error

Failed to cache esbuild-wasm@${version} at ${destDir}

Error message

Failed to cache esbuild-wasm@${version} at ${destDir}

What it means

Thrown by ensureWasmPackage when, after extracting to a unique temp dir and attempting fs.renameSync into the cache location, the destination dir still has no lib/main.js. The rename is wrapped in a try/catch that tolerates races (another process won) and cross-device renames, but if the cache then looks unusable, the package could not be materialized on disk at all. This means a filesystem-level problem, not a download problem (the tarball was already validated).

Source

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

  // 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);
  } catch {
    // Another process won the race, or rename across devices failed; clean up
    // and let the existsSync check below decide whether the cache is usable.
    fs.rmSync(tmpDir, { recursive: true, force: true });
  }
  if (!fs.existsSync(path.join(destDir, "lib", "main.js"))) {
    throw new Error(`Failed to cache esbuild-wasm@${version} at ${destDir}`);
  }
  return destDir;
}

/**
 * Resolves a tar entry to an absolute path inside destDir, stripping the leading
 * "package/" component that npm tarballs use. Returns null if the entry would
 * escape destDir (tar-slip), since WINDMILL_ESBUILD_WASM_URL allows untrusted
 * tarball sources.
 */
export function resolveTarEntryPath(
  destDir: string,
  entryName: string
): string | null {
  const rel = entryName.replace(/^[^/]+\//, "");
  const root = path.resolve(destDir);
  const outPath = path.resolve(root, rel);
  if (outPath !== root && !outPath.startsWith(root + path.sep)) {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Inspect the cache dir (`ls ~/.cache/windmill/esbuild-wasm-*`) and delete any broken/partial `esbuild-wasm-<version>` directory, then retry.
  2. Fix permissions/disk space on the cache directory so rename and writes succeed.
  3. Set WINDMILL_CACHE_DIR to a writable location on the same filesystem as TMPDIR so renameSync works.
  4. Avoid concurrent first-runs sharing one cache, or use WINDMILL_ESBUILD_WASM_PATH to bypass caching entirely.
  5. Fall back to WINDMILL_ESBUILD_WASM_PATH pointing at a pre-extracted package if the cache stays unusable.

Example fix

// before: cache dir on a read-only mount
XDG_CACHE_HOME=/ro/cache
// after
export WINDMILL_CACHE_DIR="$HOME/.cache/windmill"
rm -rf "$HOME/.cache/windmill/esbuild-wasm-0.28.0"  # clear partial extraction
wmill sync push
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs";
import * as path from "node:path";
const cacheDir = process.env.WINDMILL_CACHE_DIR ?? path.join(process.env.XDG_CACHE_HOME ?? path.join(require("os").homedir(), ".cache"), "windmill");
const destDir = path.join(cacheDir, "esbuild-wasm-0.28.0");
// pre-run check: clear partial extractions and ensure the cache location is writable
if (fs.existsSync(destDir) && !fs.existsSync(path.join(destDir, "lib", "main.js"))) {
  fs.rmSync(destDir, { recursive: true, force: true });
}
fs.mkdirSync(cacheDir, { recursive: true });
fs.accessSync(cacheDir, fs.constants.W_OK);

Try / catch

try {
  await getEsbuild();
} catch (e) {
  if (String(e).includes("Failed to cache esbuild-wasm")) {
    console.error("Cache dir unwritable or cross-device rename failed — set WINDMILL_CACHE_DIR to a writable dir and remove partial esbuild-wasm-* dirs");
  }
  throw e;
}

Prevention

When it happens

Trigger: renameSync failed (e.g. destination on a different filesystem, or lost a race with a concurrent wmill process that then left an incomplete/absent destDir) AND the subsequent existsSync check on <cacheDir>/esbuild-wasm-<version>/lib/main.js fails.

Common situations: WINDMILL_CACHE_DIR or XDG_CACHE_HOME pointing at a different mount than /tmp-based temp space (cross-device rename); parallel `wmill sync push` processes racing on a shared cache with unusual permissions; read-only or full cache directory; an earlier crashed run leaving a corrupt destDir that blocks rename and contains no main.js.

Related errors


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