Skip to content

fix(ci): Prevent immutable release failures with defensive checks#17

Merged
behrangsa merged 1 commit intomasterfrom
fix/immutable-release-prevention
Oct 7, 2025
Merged

fix(ci): Prevent immutable release failures with defensive checks#17
behrangsa merged 1 commit intomasterfrom
fix/immutable-release-prevention

Conversation

@behrangsa
Copy link
Contributor

Summary

This PR fixes the "Cannot upload assets to an immutable release" error that caused the release workflow to fail after creating a release with 0 assets.

  • Root cause: Release created before artifacts were verified, resulting in immutable published releases with no assets
  • Impact: Releases could not be modified after creation, requiring manual deletion
  • Fix: Two defensive checks added to verify artifacts and detect broken releases

Key Changes

🛡️ Defensive Checks Added

1. Verify downloaded artifacts (.github/workflows/release.yml:540-574)

  • Checks release-artifacts directory exists and contains files
  • Counts and validates archive files (.tar.gz, .zip)
  • Lists all artifacts for debugging
  • Fails fast if no artifacts found before release creation

2. Check for existing release (.github/workflows/release.yml:576-609)

  • Detects if release already exists before attempting to create it
  • Identifies broken releases (published with 0 assets from previous failed runs)
  • Automatically deletes broken releases to allow recreation
  • Prevents overwriting valid published releases that already have assets
  • Uses GitHub CLI to check release status and asset count

🐛 Bug Fixed

The workflow was failing with this error:

Error: Cannot upload assets to an immutable release. - https://docs.github.com/rest

Sequence of events:

  1. softprops/action-gh-release creates release with draft: false
  2. Release immediately becomes published (immutable)
  3. Action attempts to upload assets
  4. GitHub API rejects asset uploads to immutable release
  5. Workflow fails, leaving empty release

Why this happened:
The download-artifact step wasn't being verified before release creation. If artifacts were missing or incomplete, the release would still be created as published, then fail when trying to add assets.

Testing

Manual verification:

  • ✅ Identified the broken v0.1.2 release (0 assets)
  • ✅ Deleted broken release using gh release delete v0.1.2
  • ✅ Verified defensive checks catch missing artifacts directory
  • ✅ Verified defensive checks catch empty artifacts directory
  • ✅ Verified defensive checks count archives correctly

Workflow validation:

  • ✅ YAML syntax verified
  • ✅ Shell scripts tested with shellcheck
  • ✅ GitHub CLI commands tested manually

CI/CD Status

This PR will trigger the following CI checks:

  • Quick checks: fmt, clippy, documentation
  • Security audit: cargo-audit, cargo-deny
  • Unit tests: Linux, macOS (ARM), Windows, Linux (musl)
  • Coverage: Tarpaulin with automatic PR comment

Note: This PR only modifies workflow files, not Rust source code.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ⭐ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔨 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧪 Testing improvements
  • 🎨 Code style/formatting
  • 🧹 Miscellaneous/chore

Related Issues

Fixes: https://github.com/nutthead/ruloc/actions/runs/18313096691/job/52146493329

This workflow run created release v0.1.2 with 0 assets, then failed when trying to upload files to the immutable release.

Breaking Changes

None - this PR only fixes CI/CD workflows and does not change any user-facing functionality or API.

Release Impact

Version bump: patch
Changelog category: CI/CD improvements
Footer tags used: $fix

This PR will be included in the next release created by release-plz.

Pre-merge Checklist

  • All commits follow conventional commit format (verified via /c command)
  • Code formatted with cargo fmt --all (N/A - no Rust code changes)
  • Clippy passes with cargo clippy --all-targets --all-features -- -D warnings
  • All tests pass with cargo test
  • Coverage ≥70% maintained with cargo tarpaulin
  • Self-review completed
  • CI checks passing (will be verified by GitHub Actions)

Additional Context

Implementation Details

Artifact verification ensures the workflow fails fast if:

  • release-artifacts/ directory doesn't exist
  • Directory is empty (no files downloaded)
  • No .tar.gz or .zip archives found

Release existence check handles three scenarios:

  1. No existing release: Proceed with creation
  2. Draft release exists: Update the draft (safe)
  3. Published release with 0 assets: Delete and recreate (fixes broken state)
  4. Published release with assets: Fail with helpful error message

Why This Matters

Without these checks:

  • Failed workflows leave empty published releases
  • Empty releases are immutable and can't be fixed
  • Manual intervention required (delete release, retrigger workflow)
  • Confusing user experience (release exists but no downloads)

With these checks:

  • Workflow fails before creating broken release
  • Broken releases from previous failures auto-detected and cleaned
  • Clear error messages for troubleshooting
  • Reliable release process

Testing Strategy

Defensive checks follow the pattern from Hard Rule 10:

if [ ! -d "expected_dir" ]; then
  echo "❌ ERROR: expected_dir not found"
  echo "Additional debugging context"
  exit 1
fi
echo "✅ Verification passed"

This ensures:

  • Clear error messages for CI logs
  • Fast failure when assumptions violated
  • Helpful debugging output
  • Explicit verification of file existence

Root cause: The release workflow created published releases before
verifying artifacts were downloaded, resulting in immutable releases
with 0 assets that could not be modified. This caused the error:
"Cannot upload assets to an immutable release."

This commit adds two critical defensive checks:

1. Verify downloaded artifacts (.github/workflows/release.yml:540-574)
   - Ensures release-artifacts directory exists and contains files
   - Counts and validates archive files (.tar.gz, .zip)
   - Lists all artifacts for debugging
   - Fails fast if no artifacts found

2. Check for existing release (.github/workflows/release.yml:576-609)
   - Detects if release already exists before creation
   - Identifies broken releases (published with 0 assets)
   - Automatically deletes broken releases to allow recreation
   - Prevents overwriting valid published releases with assets

These checks ensure all artifacts are present and verified before
creating the GitHub release, preventing immutable release errors.

$fix
@github-actions
Copy link

github-actions bot commented Oct 7, 2025

📊 Coverage Report

Metric Covered Total Rate
Lines 312 388 80.41%
Branches 0 0 N/A
Additional Metric Value
Complexity 0
Files Reported 1
Report Timestamp 2025-10-07T13:01:13.000Z
View detailed report
src/main.rs: 80.4%

Generated by cargo-tarpaulin

@behrangsa behrangsa merged commit edb65e2 into master Oct 7, 2025
8 checks passed
@behrangsa behrangsa deleted the fix/immutable-release-prevention branch October 7, 2025 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments