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
- Inspect the cache dir (`ls ~/.cache/windmill/esbuild-wasm-*`) and delete any broken/partial `esbuild-wasm-<version>` directory, then retry.
- Fix permissions/disk space on the cache directory so rename and writes succeed.
- Set WINDMILL_CACHE_DIR to a writable location on the same filesystem as TMPDIR so renameSync works.
- Avoid concurrent first-runs sharing one cache, or use WINDMILL_ESBUILD_WASM_PATH to bypass caching entirely.
- 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
- Point WINDMILL_CACHE_DIR (or XDG_CACHE_HOME) at a writable directory, ideally on the same filesystem as TMPDIR.
- Delete any partial `esbuild-wasm-<version>` directories after a crashed run before retrying.
- Avoid running many concurrent wmill processes against a cold shared cache; warm it once first.
- Use WINDMILL_ESBUILD_WASM_PATH to bypass the cache entirely in constrained environments.
- Check disk space/permissions on the cache mount.
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
- File not found: ${filePath}
- Workspace folder not found, are you in the right directory?
- Directory not found: ${targetDirectory}
- Path is not a directory: ${targetDirectory}
- file path must refer to a file.
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/e881013d5861d817.
Report an issue: GitHub.