Hmbown/CodeWhale · error · Error

Release asset directory must be flat; found: ${nonFiles.map(

Error message

Release asset directory must be flat; found: ${nonFiles.map((entry) => entry.name).join(", ")}

What it means

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.

Source

Thrown at scripts/release/assemble-release-assets.js:85

  const manifestPath = path.join(directory, manifestName);
  const checksums = parseChecksumManifest(
    await fs.readFile(manifestPath, "utf8"),
    manifestName,
  );
  assertExactNames([...checksums.keys()], expectedNames, manifestName);
  for (const name of expectedNames) {
    const actual = await sha256(path.join(directory, name));
    if (checksums.get(name) !== actual) {
      throw new Error(`${manifestName} checksum mismatch for ${name}`);
    }
  }
}

async function verifyAssetDirectory(directory) {
  const entries = await fs.readdir(directory, { withFileTypes: true });
  const nonFiles = entries.filter((entry) => !entry.isFile());
  if (nonFiles.length > 0) {
    throw new Error(
      `Release asset directory must be flat; found: ${nonFiles.map((entry) => entry.name).join(", ")}`,
    );
  }

  const expected = allReleaseAssetNames();
  assertExactNames(entries.map((entry) => entry.name), expected, "Release asset directory");
  await assertManifest(directory, CHECKSUM_MANIFEST, checksummedReleaseAssetNames());
  await assertManifest(directory, BUNDLE_CHECKSUM_MANIFEST, BUNDLE_ASSET_NAMES);
  console.log(`Verified ${expected.length} release assets in ${directory}`);
}

function windowsLauncherContents() {
  return [
    "@echo off",
    "where wt >nul 2>nul",
    "set NO_ANIMATIONS=1",
    'if "%ERRORLEVEL%"=="0" (',
    '    wt --title Codewhale cmd /k "%~dp0codewhale-windows-x64.exe"',

View on GitHub (pinned to 8880682c63)

Solutions

  1. Flatten the directory: move every file out of subdirectories so only regular files remain at the top level
  2. Replace symlinks with real copies (cp -L) before verify
  3. Re-run node scripts/release/assemble-release-assets.js --verify ASSET_DIR after cleanup

Example fix

# before: assets/codewhale-bundles/ (directory) triggers the error

# after
cp -L assets/codewhale-bundles/* assets/
rmdir assets/codewhale-bundles
node scripts/release/assemble-release-assets.js --verify assets
Defensive patterns

Strategy: validation

Validate before calling

const fs = require("node:fs/promises");
async function listNonFileEntries(dir) {
  const entries = await fs.readdir(dir, { withFileTypes: true });
  return entries.filter((entry) => !entry.isFile()).map((entry) => entry.name);
}
// before verify:
const nonFiles = await listNonFileEntries(assetDir);
if (nonFiles.length > 0) throw new Error(`clean these non-file entries first: ${nonFiles.join(", ")}`);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/dc67a08be4ebf062. Report an issue: GitHub.