Cleanup Old Releases Workflow
File: .github/workflows/cleanup-old-releases.yml
Purpose: Automatically maintain a limited number of releases to prevent repository bloat
Overview
Section titled “Overview”The Cleanup Old Releases workflow automatically manages repository releases by implementing a retention policy that keeps only the most relevant releases while removing outdated ones. This helps maintain repository performance and reduces storage costs.
Retention Policy
Section titled “Retention Policy”The workflow implements a carefully designed retention strategy:
- Major Versions: Keep up to 5 latest major versions (e.g., v5.x.x, v4.x.x, v3.x.x, v2.x.x, v1.x.x)
- Current Major: Keep up to 10 latest minor/patch releases for the current (newest) major version only
- Older Majors: Keep only the latest release for each older major version
Example Retention Logic
Section titled “Example Retention Logic”// Before cleanup:v3.2.5, v3.2.4, v3.2.3, v3.2.2, v3.2.1, v3.1.9, v3.1.8, v3.1.7, v3.1.6, v3.1.5, v3.1.4, v3.1.3v2.8.1, v2.8.0, v2.7.9, v2.7.8, v2.7.7, v2.7.6v1.15.2, v1.15.1, v1.15.0
// After cleanup:v3.2.5, v3.2.4, v3.2.3, v3.2.2, v3.2.1, v3.1.9, v3.1.8, v3.1.7, v3.1.6, v3.1.5 (10 kept - current major)v2.8.1 (1 kept - latest of v2.x.x)v1.15.2 (1 kept - latest of v1.x.x)
// Deleted:// - v3.1.4, v3.1.3 (exceeded 10 limit for current major v3.x.x)// - v2.8.0, v2.7.9, v2.7.8, v2.7.7, v2.7.6 (older releases from v2.x.x)// - v1.15.1, v1.15.0 (older releases from v1.x.x)Triggers
Section titled “Triggers”The workflow runs in three scenarios:
1. Scheduled Execution
Section titled “1. Scheduled Execution”- When: Every Sunday at 2:00 AM UTC
- Purpose: Regular maintenance to keep releases within limits
- Mode: Live execution (actually deletes releases)
2. After New Releases
Section titled “2. After New Releases”- When: Automatically triggered when new tags are pushed (v* pattern)
- Purpose: Immediate cleanup after publishing new releases
- Mode: Live execution (actually deletes releases)
3. Manual Execution
Section titled “3. Manual Execution”- When: On-demand via GitHub Actions UI or CLI
- Purpose: Testing, emergency cleanup, or maintenance
- Mode: Dry-run by default (shows what would be deleted)
Workflow Parameters
Section titled “Workflow Parameters”Input Parameters
Section titled “Input Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
dry_run | boolean | true | When true, shows what would be deleted without actually deleting |
Jobs and Steps
Section titled “Jobs and Steps”Job: cleanup-releases
Section titled “Job: cleanup-releases”Runtime: Ubuntu Latest
Permissions: contents: write (required for deleting releases and tags)
Steps Overview
Section titled “Steps Overview”-
Checkout Repository
- Uses
actions/checkout@v4 - Provides access to repository metadata
- Uses
-
Setup Node.js
- Uses
actions/setup-node@v4with Node.js 20 - Required for GitHub API operations
- Uses
-
Cleanup Old Releases
- Main logic implemented in GitHub Script
- Comprehensive release analysis and cleanup
-
Summary
- Displays retention policy and execution information
- Provides guidance for manual execution
Detailed Cleanup Process
Section titled “Detailed Cleanup Process”The main cleanup step performs the following operations:
-
Fetch All Releases
const { data: releases } = await github.rest.repos.listReleases({owner: context.repo.owner,repo: context.repo.repo,per_page: 100,}); -
Parse Semantic Versions
- Validates each release tag against semantic version format (
v1.2.3) - Skips releases with invalid version formats
- Extracts major, minor, patch, and prerelease components
- Validates each release tag against semantic version format (
-
Sort and Group
- Sorts releases by semantic version (newest first)
- Groups releases by major version number
- Identifies current (latest) major version
-
Apply Retention Rules
- Current Major: Keep up to 10 most recent releases
- Older Majors: Keep only the latest release from each major version
- Excess Majors: Mark entire major versions for deletion (beyond 5 major limit)
-
Safety Analysis
- Calculates what will be kept vs. deleted
- Displays detailed summary before any actions
- Shows exact release names and creation dates
-
Execute Deletions (if not dry-run)
- Deletes releases via GitHub API
- Removes associated Git tags
- Includes rate limiting to avoid API limits
- Continues on individual failures
- Reports success/failure statistics
Execution Modes
Section titled “Execution Modes”Dry Run Mode (Default)
Section titled “Dry Run Mode (Default)”Purpose: Safe analysis and testing
# Manual execution (dry run)gh workflow run "Cleanup Old Releases"# OR explicitlygh workflow run "Cleanup Old Releases" -f dry_run=trueBehavior:
- ✅ Analyzes all releases and shows detailed report
- ✅ Displays exactly what would be deleted
- ✅ No actual deletions performed
- ✅ Safe to run anytime
Sample Output:
🧹 Starting release cleanup (dry run: true)📦 Found 25 total releases📊 Parsed 25 valid releases🏷️ Found 3 major version groups: v3.x.x, v2.x.x, v1.x.x📌 Keeping major versions: v3.x.x, v2.x.x, v1.x.x📦 Current major v3: keeping 10/15 releases📦 Older major v2: keeping 1/6 releases (latest only)📦 Older major v1: keeping 1/3 releases (latest only)
📊 Summary: ✅ Releases to keep: 12 🗑️ Releases to delete: 13
📋 Releases to delete: - v3.1.4 (3.1.4) - created 2024-10-15T10:30:00Z - v3.1.3 (3.1.3) - created 2024-10-10T14:20:00Z [...]
🔍 DRY RUN: No releases were actually deleted.💡 To perform the actual cleanup, run this workflow with 'dry_run' set to false.Live Execution Mode
Section titled “Live Execution Mode”Purpose: Actually delete releases
# Manual execution (live deletions)gh workflow run "Cleanup Old Releases" -f dry_run=falseBehavior:
- 🗑️ Actually deletes old releases and associated Git tags
- ⚠️ Irreversible - deleted releases cannot be recovered
- 📊 Reports deletion success/failure statistics
- ⏱️ Includes delays between API calls to avoid rate limiting
Sample Output:
[... same analysis as dry run ...]
🗑️ Starting deletion process...🗑️ Deleting release: v3.1.4🏷️ Deleted tag: v3.1.4🗑️ Deleting release: v3.1.3🏷️ Deleted tag: v3.1.3[...]
✅ Cleanup completed! 🗑️ Successfully deleted: 13 releases 📦 Remaining releases: 12Automatic Execution
Section titled “Automatic Execution”Scheduled (Weekly):
- Runs every Sunday at 2:00 AM UTC
- Uses live execution mode (
dry_run=false) - Provides consistent maintenance without manual intervention
After Releases (Triggered):
- Runs automatically when new version tags are pushed
- Uses live execution mode (
dry_run=false) - Keeps release history current immediately after new releases
Error Handling and Safety
Section titled “Error Handling and Safety”Built-in Safety Features
Section titled “Built-in Safety Features”- Dry Run Default: Manual executions default to safe mode
- Detailed Logging: Shows exactly what will happen before doing it
- Version Validation: Skips releases with invalid version formats
- Graceful Failures: Continues cleanup even if individual deletions fail
- Rate Limiting: Includes delays to avoid GitHub API rate limits
Error Scenarios
Section titled “Error Scenarios”| Error | Cause | Behavior |
|---|---|---|
| Permission Denied | Insufficient GitHub token permissions | Workflow fails with clear error message |
| Invalid Version Format | Release tag doesn’t follow semantic versioning | Skips the release, continues with others |
| Individual Deletion Failure | API error for specific release | Logs error, continues with remaining deletions |
| Rate Limiting | Too many API calls | Built-in delays prevent this; retry later if needed |
| No Releases Found | Repository has no releases | Exits gracefully with informational message |
Use Cases
Section titled “Use Cases”Regular Maintenance
Section titled “Regular Maintenance”- Automated: Weekly cleanup keeps release history manageable
- Post-Release: Immediate cleanup after new releases maintains current limits
- Storage: Reduces repository storage used by old release artifacts
Repository Health
Section titled “Repository Health”- Performance: Fewer releases improve GitHub UI performance
- Organization: Clean release history makes navigation easier
- Compliance: Meet organizational data retention policies
Development Workflow
Section titled “Development Workflow”- CI/CD Integration: Automatically triggered after release publishing
- Testing: Dry-run mode allows safe testing before live execution
- Emergency: Manual execution for immediate cleanup when needed
Monitoring and Troubleshooting
Section titled “Monitoring and Troubleshooting”Monitoring Commands
Section titled “Monitoring Commands”# Check recent workflow runsgh run list --workflow="cleanup-old-releases.yml" --limit 10
# View specific workflow executiongh run view <run-id> --log
# List current releasesgh release list --limit 20
# Count releases by major versiongh release list --json tagName --jq '.[].tagName' | grep -E '^v[0-9]+\.' | cut -d. -f1 | sort | uniq -cCommon Issues and Solutions
Section titled “Common Issues and Solutions”No Releases Deleted
Section titled “No Releases Deleted”- Cause: All releases are within retention limits
- Solution: Normal operation, no action needed
- Check: Verify current release count vs. limits
Permission Errors
Section titled “Permission Errors”- Cause:
GITHUB_TOKENlackscontents: writepermission - Solution: Check repository workflow permissions
- Verify: Ensure workflow has proper permissions block
Workflow Not Triggering
Section titled “Workflow Not Triggering”- Cause: Branch protection or workflow permissions
- Solution: Check repository settings and workflow file syntax
- Debug: Use manual execution to test functionality
Rate Limiting Errors
Section titled “Rate Limiting Errors”- Cause: Too many API calls in short period
- Solution: Workflow includes built-in delays; retry later
- Prevention: Don’t run multiple instances simultaneously
Debug Commands
Section titled “Debug Commands”# Test with dry rungh workflow run "Cleanup Old Releases" -f dry_run=true
# Check workflow file syntaxnpx yaml valid .github/workflows/cleanup-old-releases.yml
# View recent releasesgh release list --json tagName,createdAt --jq '.[] | "\(.tagName) - \(.createdAt)"' | head -20
# Check repository permissionsgh api repos/:owner/:repo --jq '.permissions'Best Practices
Section titled “Best Practices”Before Implementation
Section titled “Before Implementation”- Test First: Always run in dry-run mode before live execution
- Backup Important: Consider backing up important release artifacts externally
- Review Policy: Ensure retention numbers match project needs
During Operation
Section titled “During Operation”- Monitor Logs: Review weekly cleanup logs for unexpected behavior
- Track Storage: Monitor repository storage usage trends
- Version Strategy: Maintain consistent semantic versioning
Maintenance
Section titled “Maintenance”- Regular Review: Periodically review retention policy effectiveness
- Adjust Numbers: Modify retention counts based on project evolution
- Documentation: Keep usage documentation current with any changes
Integration with Other Workflows
Section titled “Integration with Other Workflows”Upstream Dependencies
Section titled “Upstream Dependencies”- Smart Version Bump: Creates new tags that trigger this workflow
- Release & Publish: Creates releases that may be subject to cleanup
Downstream Effects
Section titled “Downstream Effects”- Storage Reduction: Reduces repository storage usage
- API Performance: Fewer releases improve GitHub API response times
- User Experience: Cleaner release history improves navigation
Security Considerations
Section titled “Security Considerations”Permissions Required
Section titled “Permissions Required”contents: write- Required to delete releases and Git tags- Repository access - Workflow must have access to target repository
Data Protection
Section titled “Data Protection”- Irreversible: Deleted releases cannot be recovered
- Git Tags: Associated tags are also deleted from Git history
- Artifacts: Release artifacts (binaries, etc.) are permanently removed
Access Control
Section titled “Access Control”- Branch Protection: Workflow file changes subject to normal PR process
- Manual Execution: Requires repository write access
- Scheduled Execution: Runs with repository-level permissions
Configuration
Section titled “Configuration”Customizing Retention Policy
Section titled “Customizing Retention Policy”To modify retention numbers, edit the workflow file:
# Keep up to 5 major versionsconst majorsToKeep = majorVersions.slice(0, 5);
# Keep up to 10 releases for current majorconst keepCount = Math.min(10, releases.length);Adjusting Schedule
Section titled “Adjusting Schedule”schedule: # Run weekly on Sundays at 2 AM UTC - cron: '0 2 * * 0' # Change to daily: '0 2 * * *' # Change to monthly: '0 2 1 * *'Modifying Triggers
Section titled “Modifying Triggers”on: push: tags: - 'v*' # Trigger on version tags # - 'release-*' # Alternative trigger patternEmergency Recovery
Section titled “Emergency Recovery”Tag Recreation
Section titled “Tag Recreation”If tags are accidentally deleted:
# Find commit hash for versiongit log --oneline --grep="v1.2.3"
# Recreate taggit tag v1.2.3 <commit-hash>git push origin v1.2.3Release Recreation
Section titled “Release Recreation”If releases are accidentally deleted:
# Recreate release from existing taggh release create v1.2.3 --title "Release v1.2.3" --notes "Recreated release"Note: Original release artifacts cannot be recovered and must be rebuilt.
This documentation covers the complete functionality of the Cleanup Old Releases workflow. For questions or issues, refer to the troubleshooting section or repository maintainers.