Hmbown/CodeWhale · error · Error

Downloaded release artifact must be a regular file: ${source

Error message

Downloaded release artifact must be a regular file: ${source}

What it means

assemble() lstat()ed an expected intermediate artifact and it exists but is not a regular file — a directory, symlink, fifo, or similar. Because lstat does not follow symlinks, a symlink to the real binary is rejected just like a directory. The guard ensures only real bytes are copied into the release.

Source

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

async function assemble(inputDirectory, outputDirectory) {
  const expected = allReleaseAssetNames();
  const generated = new Set([WINDOWS_LAUNCHER, CHECKSUM_MANIFEST]);
  const copiedNames = expected.filter((name) => !generated.has(name));
  const sources = new Map();
  for (const name of copiedNames) {
    const source = intermediateArtifactPath(inputDirectory, name);
    let sourceStat;
    try {
      sourceStat = await fs.lstat(source);
    } catch (error) {
      if (error && error.code === "ENOENT") {
        throw new Error(`Downloaded release artifacts are missing ${name} at ${source}`);
      }
      throw error;
    }
    if (!sourceStat.isFile()) {
      throw new Error(`Downloaded release artifact must be a regular file: ${source}`);
    }
    sources.set(name, source);
  }

  await fs.mkdir(outputDirectory, { recursive: true });
  const existing = await fs.readdir(outputDirectory);
  if (existing.length > 0) {
    throw new Error(`Output directory must be empty: ${outputDirectory}`);
  }

  for (const name of copiedNames) {
    await fs.copyFile(sources.get(name), path.join(outputDirectory, name));
  }
  await fs.writeFile(
    path.join(outputDirectory, WINDOWS_LAUNCHER),
    windowsLauncherContents(),
    "utf8",
  );

View on GitHub (pinned to 8880682c63)

Solutions

  1. Make the path a real regular file: replace the symlink with its target's bytes (cp -L) or flatten the extra directory level
  2. Fix the unpack step so each artifact lands as a file at INPUT_DIR/<name>/<name>
  3. Re-run assemble after correcting the INPUT_DIR layout

Example fix

# before: artifacts/codewhale-x86_64/codewhale-x86_64.tar.gz is a symlink -> rejected

# after
cp -L artifacts/codewhale-x86_64/codewhale-x86_64.tar.gz /tmp/real.tar.gz
mv /tmp/real.tar.gz artifacts/codewhale-x86_64/codewhale-x86_64.tar.gz
Defensive patterns

Strategy: validation

Validate before calling

const fs = require("node:fs/promises");
const path = require("node:path");
async function assertRegularFile(file) {
  const stat = await fs.lstat(file); // lstat, like the script: does not follow symlinks
  if (!stat.isFile()) throw new Error(`${file} is not a regular file; flatten or copy with cp -L`);
}

Prevention

When it happens

Trigger: INPUT_DIR/<name>/<name> resolving to a directory because the artifact unpacked with an extra nesting level; artifacts replaced by symlinks from dedup tooling or manual 'save disk' setups; a fifo or socket accidentally created at the path.

Common situations: An unpack/download step creating directories where files are expected; artifact caching layers that dedupe via symlinks; workflow changes altering the artifact layout.

Related errors


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