Hmbown/CodeWhale · error · Error
GitHub Release asset set is stale for workflow run
Error message
GitHub Release asset set is stale for workflow run ${run.database_id || run.id}: ${stale.join("; ")} What it means
After confirming all expected assets exist, assertReleaseAssetsFresh compares each asset's updated_at against the freshness baseline (the successful release job's started_at, falling back to run timestamps). Assets older than the baseline are considered stale and it throws listing each stale asset with its timestamp.
Solutions
- Delete the release (or its assets) and re-run the Release workflow so all assets are freshly built and uploaded.
- Ensure the release job started before asset upload timestamps (check clock skew if times are close).
- Never re-tag an existing version; use a new version string.
Example fix
// before git tag -f v1.2.3 HEAD~1 # stale assets kept // after gh release delete v1.2.3 --cleanup-tag && git tag v1.2.3 <sha> && gh workflow run release.yml
Defensive patterns
Strategy: validation
Validate before calling
const baseline = Date.parse(run.release_job_started_at ?? run.run_started_at); const stale = expected.filter(n => Date.parse(assetByName(n).updated_at) < baseline); if (stale.length) console.error("Stale assets:", stale); Type guard
null
Try / catch
try { verifyFreshness(); } catch (e) { if (e.message.includes("asset set is stale")) { console.error("Delete the release and re-run the Release workflow for this tag."); process.exit(1); } throw e; } Prevention
- Never re-tag an existing version; bump the version instead
- Delete old releases before re-releasing the same tag
- Build and upload assets only inside the release job
When it happens
Trigger: An asset's updatedAt < freshnessBaseline — i.e., the release asset predates the workflow run that should have produced it, indicating leftovers from an earlier run or a re-tagged release.
Common situations: Re-tagging a version without deleting the old release, so old assets survive; manually attaching binaries built from a different commit; concurrency skipping upload while old assets remain.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- GitHub Release is missing required release asset(s)
- returned HTTP ( )
- GitHub release request failed with HTTP
- A positive pull request number is required
- Annotated tag did not peel to a commit SHA
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/35c85117da3b3bc2.
Report an issue: GitHub.
Appendix: source
Thrown at npm/codewhale/scripts/verify-release-assets.js:293
// job baseline is unavailable.
const baseline = run.release_job_started_at || run.run_started_at || run.created_at;
const baselineLabel = run.release_job_started_at ? "release job start" : "workflow run start";
const freshnessBaseline = parseGitHubTime(baseline, baselineLabel);
const stale = [];
for (const expected of expectedAssets) {
const asset = assetsByName.get(expected);
if (asset.state && asset.state !== "uploaded") {
stale.push(`${expected} has state ${asset.state}`);
continue;
}
const updatedAt = parseGitHubTime(asset.updated_at || asset.created_at, `${expected} update`);
if (updatedAt < freshnessBaseline) {
stale.push(`${expected} updated at ${asset.updated_at || asset.created_at}`);
}
}
if (stale.length > 0) {
throw new Error(
`GitHub Release asset set is stale for workflow run ${run.database_id || run.id}: ${stale.join("; ")}`,
);
}
}
async function verifyGitHubReleaseFreshness(repo, version, expectedAssets) {
const tag = `v${version}`;
const tagSha = await resolveTagCommitSha(repo, tag);
const release = await githubApi(repo, `/releases/tags/${encodeURIComponent(tag)}`);
const run = await findReleaseWorkflowRun(repo, tag, tagSha);
assertReleaseAssetsFresh(release, expectedAssets, run);
console.log(
`GitHub release asset freshness OK: ${expectedAssets.length} release assets for ${tag} were produced by run ${run.database_id || run.id} at ${tagSha.slice(0, 12)}.`,
);
}
function parseChecksumManifest(text) {
const checksums = new Map();View on GitHub (pinned to 433685b202)