{"record":{"id":"2c8f507c203fd65a","repo":"headroomlabs-ai/headroom","slug":"assets-directory-must-be-empty-to-avoid-stale-tarb","errorCode":null,"errorMessage":"Assets directory must be empty to avoid stale tarballs: ${assetsDir}","messagePattern":"Assets directory must be empty to avoid stale tarballs: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build_npm_release_assets.mjs","lineNumber":113,"sourceCode":"}\n\nfunction runNode(args, cwd) {\n  run(process.execPath, args, cwd);\n}\n\nfunction readJson(filePath) {\n  return JSON.parse(readFileSync(filePath, \"utf8\"));\n}\n\nfunction writeJson(filePath, data) {\n  writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\\n`, \"utf8\");\n}\n\nfunction ensureEmptyAssetsDir() {\n  mkdirSync(assetsDir, { recursive: true });\n  const existing = readdirSync(assetsDir);\n  if (existing.length > 0) {\n    throw new Error(\n      `Assets directory must be empty to avoid stale tarballs: ${assetsDir}`,\n    );\n  }\n}\n\nfunction restoreTrackedFiles() {\n  for (const [filePath, contents] of snapshots.entries()) {\n    if (contents === null) {\n      rmSync(filePath, { force: true });\n    } else {\n      mkdirSync(path.dirname(filePath), { recursive: true });\n      writeFileSync(filePath, contents, \"utf8\");\n    }\n  }\n}\n\nfunction relativeFileSpec(fromDir, targetPath) {\n  let relativePath = path.relative(fromDir, targetPath).split(path.sep).join(\"/\");","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/scripts/build_npm_release_assets.mjs#L95-L131","documentation":"The release asset script deliberately requires the assets output directory to be empty before building, so that no tarballs from a previous (possibly different-version) run can leak into the release. ensureEmptyAssetsDir() creates the directory if needed, then throws if it contains anything at all. This is a self-imposed guard against publishing stale artifacts.","triggerScenarios":"Running scripts/build_npm_release_assets.mjs when the assets directory already has files — a previous aborted/partial run, manual test builds dropped there, or a CI workspace/cache that persists the directory between jobs.","commonSituations":"Rerunning the script after a late-stage failure left partial tarballs; CI workspace caching that includes the assets dir; committing or copying earlier release artifacts into the output folder.","solutions":["Delete the directory contents and rerun: rm -rf <assetsDir> (the script recreates it)","In CI, ensure the checkout/workspace cache does not persist the assets directory between jobs","If artifacts there are precious, move them elsewhere before rerunning instead of deleting"],"exampleFix":"# before\nnode scripts/build_npm_release_assets.mjs  # throws: assets dir not empty\n\n# after\nrm -rf release-assets/  # or whatever assetsDir the script prints\nnode scripts/build_npm_release_assets.mjs","handlingStrategy":"validation","validationCode":"import { readdirSync, rmSync } from \"node:fs\";\n\n// Precheck before running the release script\nconst entries = readdirSync(assetsDir);\nif (entries.length > 0) {\n  throw new Error(\n    `Assets dir not empty (${entries.length} files) — run: rm -rf ${assetsDir}`\n  );\n}","typeGuard":"import { readdirSync } from \"node:fs\";\n\nfunction isAssetsDirClean(dir: string): boolean {\n  return readdirSync(dir).length === 0;\n}","tryCatchPattern":null,"preventionTips":["Clean the assets directory as the first step of every release runbook/CI job (rm -rf before build)","Do not cache the assets directory between CI runs","Never manually stage artifacts into the output dir; let the script produce everything fresh"],"tags":["build","release","ci","hygiene"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}