{"record":{"id":"dc67a08be4ebf062","repo":"Hmbown/CodeWhale","slug":"release-asset-directory-must-be-flat-found-non","errorCode":null,"errorMessage":"Release asset directory must be flat; found: ${nonFiles.map((entry) => entry.name).join(\", \")}","messagePattern":"Release asset directory must be flat; found: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/release/assemble-release-assets.js","lineNumber":85,"sourceCode":"  const manifestPath = path.join(directory, manifestName);\n  const checksums = parseChecksumManifest(\n    await fs.readFile(manifestPath, \"utf8\"),\n    manifestName,\n  );\n  assertExactNames([...checksums.keys()], expectedNames, manifestName);\n  for (const name of expectedNames) {\n    const actual = await sha256(path.join(directory, name));\n    if (checksums.get(name) !== actual) {\n      throw new Error(`${manifestName} checksum mismatch for ${name}`);\n    }\n  }\n}\n\nasync function verifyAssetDirectory(directory) {\n  const entries = await fs.readdir(directory, { withFileTypes: true });\n  const nonFiles = entries.filter((entry) => !entry.isFile());\n  if (nonFiles.length > 0) {\n    throw new Error(\n      `Release asset directory must be flat; found: ${nonFiles.map((entry) => entry.name).join(\", \")}`,\n    );\n  }\n\n  const expected = allReleaseAssetNames();\n  assertExactNames(entries.map((entry) => entry.name), expected, \"Release asset directory\");\n  await assertManifest(directory, CHECKSUM_MANIFEST, checksummedReleaseAssetNames());\n  await assertManifest(directory, BUNDLE_CHECKSUM_MANIFEST, BUNDLE_ASSET_NAMES);\n  console.log(`Verified ${expected.length} release assets in ${directory}`);\n}\n\nfunction windowsLauncherContents() {\n  return [\n    \"@echo off\",\n    \"where wt >nul 2>nul\",\n    \"set NO_ANIMATIONS=1\",\n    'if \"%ERRORLEVEL%\"==\"0\" (',\n    '    wt --title Codewhale cmd /k \"%~dp0codewhale-windows-x64.exe\"',","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/release/assemble-release-assets.js#L67-L103","documentation":"verifyAssetDirectory() requires the release asset directory to contain only regular files at the top level; any subdirectory, symlink, or other non-file entry triggers this error with the offending names listed. Release assets are flat by design and symlinks would defeat content hashing and reproducible packaging.","triggerScenarios":"A subdirectory inside the asset dir (e.g. codewhale-bundles/ copied wholesale instead of its contents); a symlinked binary (with withFileTypes the entry is a symlink, and isFile() is false for it); fifos, sockets, or device nodes created by accident.","commonSituations":"CI step copying a folder instead of its contents; local runs symlinking large artifacts to save disk; unzipping an archive that preserves a top-level directory; a mount point inside the directory.","solutions":["Flatten the directory: move every file out of subdirectories so only regular files remain at the top level","Replace symlinks with real copies (cp -L) before verify","Re-run node scripts/release/assemble-release-assets.js --verify ASSET_DIR after cleanup"],"exampleFix":"# before: assets/codewhale-bundles/ (directory) triggers the error\n\n# after\ncp -L assets/codewhale-bundles/* assets/\nrmdir assets/codewhale-bundles\nnode scripts/release/assemble-release-assets.js --verify assets","handlingStrategy":"validation","validationCode":"const fs = require(\"node:fs/promises\");\nasync function listNonFileEntries(dir) {\n  const entries = await fs.readdir(dir, { withFileTypes: true });\n  return entries.filter((entry) => !entry.isFile()).map((entry) => entry.name);\n}\n// before verify:\nconst nonFiles = await listNonFileEntries(assetDir);\nif (nonFiles.length > 0) throw new Error(`clean these non-file entries first: ${nonFiles.join(\", \")}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Copy artifact files, not folders, into the asset directory (cp dir/* rather than cp -r dir)","Never symlink artifacts into the release directory — copy real bytes","Check the directory with ls -la for stray mounts or dotfiles before verify in CI"],"tags":["release","filesystem","validation","ci"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}