{"record":{"id":"e881013d5861d817","repo":"windmill-labs/windmill","slug":"failed-to-cache-esbuild-wasm-version-at-destd","errorCode":null,"errorMessage":"Failed to cache esbuild-wasm@${version} at ${destDir}","messagePattern":"Failed to cache esbuild-wasm@(.+?) at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/utils/esbuild_loader.ts","lineNumber":153,"sourceCode":"  // Extract to a unique temp dir and rename into place so a crash or a\n  // concurrent writer can't leave a half-extracted package behind, and so two\n  // extractions never share an in-progress directory.\n  const tmpDir = `${destDir}.${process.pid}.${extractCounter++}.tmp`;\n  fs.rmSync(tmpDir, { recursive: true, force: true });\n  await extractTarball(res.body, tmpDir);\n  if (!fs.existsSync(path.join(tmpDir, \"lib\", \"main.js\"))) {\n    fs.rmSync(tmpDir, { recursive: true, force: true });\n    throw new Error(`esbuild-wasm@${version} tarball did not contain lib/main.js`);\n  }\n  try {\n    fs.renameSync(tmpDir, destDir);\n  } catch {\n    // Another process won the race, or rename across devices failed; clean up\n    // and let the existsSync check below decide whether the cache is usable.\n    fs.rmSync(tmpDir, { recursive: true, force: true });\n  }\n  if (!fs.existsSync(path.join(destDir, \"lib\", \"main.js\"))) {\n    throw new Error(`Failed to cache esbuild-wasm@${version} at ${destDir}`);\n  }\n  return destDir;\n}\n\n/**\n * Resolves a tar entry to an absolute path inside destDir, stripping the leading\n * \"package/\" component that npm tarballs use. Returns null if the entry would\n * escape destDir (tar-slip), since WINDMILL_ESBUILD_WASM_URL allows untrusted\n * tarball sources.\n */\nexport function resolveTarEntryPath(\n  destDir: string,\n  entryName: string\n): string | null {\n  const rel = entryName.replace(/^[^/]+\\//, \"\");\n  const root = path.resolve(destDir);\n  const outPath = path.resolve(root, rel);\n  if (outPath !== root && !outPath.startsWith(root + path.sep)) {","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/cli/src/utils/esbuild_loader.ts#L135-L171","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: cache dir on a read-only mount\nXDG_CACHE_HOME=/ro/cache\n// after\nexport WINDMILL_CACHE_DIR=\"$HOME/.cache/windmill\"\nrm -rf \"$HOME/.cache/windmill/esbuild-wasm-0.28.0\"  # clear partial extraction\nwmill sync push","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nconst cacheDir = process.env.WINDMILL_CACHE_DIR ?? path.join(process.env.XDG_CACHE_HOME ?? path.join(require(\"os\").homedir(), \".cache\"), \"windmill\");\nconst destDir = path.join(cacheDir, \"esbuild-wasm-0.28.0\");\n// pre-run check: clear partial extractions and ensure the cache location is writable\nif (fs.existsSync(destDir) && !fs.existsSync(path.join(destDir, \"lib\", \"main.js\"))) {\n  fs.rmSync(destDir, { recursive: true, force: true });\n}\nfs.mkdirSync(cacheDir, { recursive: true });\nfs.accessSync(cacheDir, fs.constants.W_OK);","typeGuard":null,"tryCatchPattern":"try {\n  await getEsbuild();\n} catch (e) {\n  if (String(e).includes(\"Failed to cache esbuild-wasm\")) {\n    console.error(\"Cache dir unwritable or cross-device rename failed — set WINDMILL_CACHE_DIR to a writable dir and remove partial esbuild-wasm-* dirs\");\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["filesystem","cache","esbuild","concurrency"],"backgroundTag":"cache-write-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}