{"record":{"id":"604e4106c3f93dd8","repo":"Hmbown/CodeWhale","slug":"output-directory-must-be-empty-outputdirectory","errorCode":null,"errorMessage":"Output directory must be empty: ${outputDirectory}","messagePattern":"Output directory must be empty: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/release/assemble-release-assets.js","lineNumber":143,"sourceCode":"    let sourceStat;\n    try {\n      sourceStat = await fs.lstat(source);\n    } catch (error) {\n      if (error && error.code === \"ENOENT\") {\n        throw new Error(`Downloaded release artifacts are missing ${name} at ${source}`);\n      }\n      throw error;\n    }\n    if (!sourceStat.isFile()) {\n      throw new Error(`Downloaded release artifact must be a regular file: ${source}`);\n    }\n    sources.set(name, source);\n  }\n\n  await fs.mkdir(outputDirectory, { recursive: true });\n  const existing = await fs.readdir(outputDirectory);\n  if (existing.length > 0) {\n    throw new Error(`Output directory must be empty: ${outputDirectory}`);\n  }\n\n  for (const name of copiedNames) {\n    await fs.copyFile(sources.get(name), path.join(outputDirectory, name));\n  }\n  await fs.writeFile(\n    path.join(outputDirectory, WINDOWS_LAUNCHER),\n    windowsLauncherContents(),\n    \"utf8\",\n  );\n\n  const checksumRows = [];\n  for (const name of [...checksummedReleaseAssetNames()].sort()) {\n    checksumRows.push(`${await sha256(path.join(outputDirectory, name))}  ${name}`);\n  }\n  await fs.writeFile(\n    path.join(outputDirectory, CHECKSUM_MANIFEST),\n    `${checksumRows.join(\"\\n\")}\\n`,","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/release/assemble-release-assets.js#L125-L161","documentation":"assemble() refuses to write into an OUTPUT_DIR that already contains any entries. Because it generates the checksum manifest over the final file set, a non-empty target could mix stale or foreign bytes into a shipped release, so it fails fast before copying anything.","triggerScenarios":"Re-running assemble into the same OUTPUT_DIR used by a previous attempt; passing a directory that already holds files (workspace dir, a folder with a README); pointing OUTPUT_DIR at INPUT_DIR by mistake.","commonSituations":"Retrying a failed release without cleaning the output directory; a CI step that pre-populates the output path; copy-paste invoking with in and out swapped.","solutions":["Use a fresh empty directory: rm -rf release-out && node scripts/release/assemble-release-assets.js INPUT_DIR release-out","In CI, use a unique run-scoped path such as $RUNNER_TEMP/release-assets","Double-check argument order: INPUT_DIR (downloaded artifacts) first, OUTPUT_DIR second"],"exampleFix":"# before: second run reuses a non-empty output dir -> error\nnode scripts/release/assemble-release-assets.js artifacts-in release-out\n\n# after\nrm -rf release-out\nnode scripts/release/assemble-release-assets.js artifacts-in release-out","handlingStrategy":"validation","validationCode":"#!/usr/bin/env bash\n# CI pre-step: guarantee a clean output dir before assemble\nOUT=\"$RUNNER_TEMP/release-assets\"\nrm -rf \"$OUT\" && mkdir -p \"$OUT\"\nnode scripts/release/assemble-release-assets.js \"$ARTIFACTS_IN\" \"$OUT\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Create the output directory fresh in the same command that runs assemble (rm -rf && mkdir -p)","Use runner-temp paths unique per job instead of workspace-relative output dirs","Never reuse a release output directory across attempts; each assemble run starts from empty"],"tags":["release","filesystem","ci","validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}