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
- Cut a new patch tag (vX.Y.(Z+1)) and release that — published bytes stay immutable by design
- Re-run only the failed job if the upload step never executed in that attempt
- 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
- 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
- Run ensure-release-assets-absent.js as a mandatory gate immediately before the upload step of every release workflow
- On partial failures, re-run only the failed job — never 'Re-run all jobs' once upload has executed
- Treat this error as a stop, not an obstacle: the fix is a new version tag, not deleting public bytes
- Manual asset deletion (gh release delete-asset) must be a documented, human-approved exception
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
- ${label} does not match the authoritative inventory; missing
- Release asset directory must be flat; found: ${nonFiles.map(
- Downloaded release artifacts are missing ${name} at ${source
- Downloaded release artifact must be a regular file: ${source
- Output directory must be empty: ${outputDirectory}
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/4d4a494dbe6e5591.
Report an issue: GitHub.