Hmbown/CodeWhale · critical · Error

Refusing to replace existing assets for ${tag}: ${inventory}

Error message

Refusing to replace existing assets for ${tag}: ${inventory}. A normal Release workflow rerun must never delete or overwrite public bytes.

What it means

The immutability guard of ensure-release-assets-absent.js: the GitHub Release for this tag already has assets (names listed, or a count if unnamed), and a normal Release workflow rerun must never delete or overwrite public bytes that consumers may already have downloaded. The script aborts the pipeline instead of letting a rerun silently replace assets.

Source

Thrown at scripts/release/ensure-release-assets-absent.js:66

  } catch (error) {
    throw new Error(`GitHub Release ${tag} returned invalid JSON: ${error.message}`);
  }
}

function assertReleaseAssetsAbsent(release, tag) {
  if (release === null) {
    return;
  }
  if (!release || !Array.isArray(release.assets)) {
    throw new Error(`GitHub Release ${tag} did not provide an asset inventory`);
  }
  if (release.assets.length === 0) {
    return;
  }

  const names = release.assets.map((asset) => asset && asset.name).filter(Boolean);
  const inventory = names.length > 0 ? names.join(", ") : `${release.assets.length} unnamed asset(s)`;
  throw new Error(
    `Refusing to replace existing assets for ${tag}: ${inventory}. ` +
      "A normal Release workflow rerun must never delete or overwrite public bytes.",
  );
}

function main() {
  if (process.argv.length !== 4) {
    throw new Error(usage());
  }
  const repo = process.argv[2];
  const tag = process.argv[3];
  const release = fetchRelease(repo, tag);
  assertReleaseAssetsAbsent(release, tag);
  console.log(
    release === null
      ? `No existing GitHub Release assets found for ${tag}.`
      : `Existing GitHub Release ${tag} has no assets; first upload may proceed.`,
  );

View on GitHub (pinned to 8880682c63)

Solutions

  1. Cut a new patch tag (vX.Y.(Z+1)) and release that — published bytes stay immutable by design
  2. Re-run only the failed job if the upload step never executed in that attempt
  3. If the assets are genuinely wrong and must be replaced, have a human deliberately delete them (gh release delete-asset OWNER/REPO TAG <asset> per file, or delete the release), acknowledging that consumers may hold the old bytes
  4. Keep this script as a pre-upload gate in the release workflow so reruns stop here instead of overwriting

Example fix

# before: re-running the Release workflow for v1.2.3 that already uploaded assets

# after: ship a new tag instead
git tag v1.2.4 && git push origin v1.2.4
# workflow releases v1.2.4; guard passes for the new tag
Defensive patterns

Strategy: validation

Validate before calling

#!/usr/bin/env bash
# pre-check before any upload step: refuse to touch a tag that already has assets
assets=$(gh api "repos/$REPO/releases/tags/$TAG" --jq '.assets | length' 2>/dev/null || echo 0)
if [ "$assets" != "0" ]; then
  echo "refusing: $TAG already has $assets asset(s); cut a new tag" >&2
  exit 1
fi

Prevention

When it happens

Trigger: Re-running the Release workflow after a partial failure where the upload step had already succeeded; re-running all jobs from scratch; the release having been created manually with assets beforehand.

Common situations: A release job failed at a late step (e.g. verification) after upload, and someone clicks 'Re-run all jobs'; retrying at workflow level rather than the single failed job; a re-cut of the same tag after a fix.

Related errors


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