Releasing Software
Overview
Never tag until CI passes. Every retag erodes trust and wastes time.
This skill prevents the "tag, watch CI fail, retag, repeat" anti-pattern by establishing a pre-flight checklist that catches issues BEFORE creating the tag.
The Iron Law
NO TAG WITHOUT GREEN CI
Run the full verification locally. Fix everything. THEN tag. Not before.
Pre-Release Checklist
MANDATORY: Use TodoWrite to create todos for EACH item.
1. Verify Build Paths
Check ALL places that reference your main package:
- •
goreleaser.yml-main:field (common:./cmd/appvs.) - •
.github/workflows/*.yml- anygo buildcommands - •
Makefile- build targets - •
Dockerfile- build commands
# Find all references to cmd/ or main package paths grep -r "cmd/" . --include="*.yml" --include="*.yaml" --include="Makefile" --include="Dockerfile" grep -r "go build" . --include="*.yml" --include="*.yaml" --include="Makefile"
2. Verify Tests Exist
Go 1.23+ coverage tool fails on packages without tests:
# Check for test files in each package find . -name "*_test.go" | wc -l # If zero, add placeholder tests to EVERY package
Every package needs at least one test file or CI coverage step fails with no such tool "covdata".
3. Verify Local CI Passes
# Run what CI runs make test # or go test ./... make lint # or golangci-lint run make build # verify it compiles # If using coverage make test-coverage
4. Verify Documentation
- • README.md exists and is current
- • CHANGELOG.md updated (if maintained)
- • Version references updated
5. Verify Release Config
- • goreleaser description is correct (not template leftovers)
- • goreleaser homepage URL is correct
- •
.gitignoreincludes build artifacts, coverage files
6. Verify Clean Git State
git status # Nothing unexpected staged git diff --stat # Review all changes git log --oneline -5 # Verify recent history
Release Procedure
Only after ALL checks pass:
# 1. Commit all changes git add -A && git commit -m "release: prepare v0.0.X" # 2. Wait for pre-commit hooks to pass # If hooks fail, fix and re-commit # 3. Push and wait for CI git push origin main # 4. WAIT FOR CI TO PASS - check status gh run list --limit 2 # OR watch in browser # 5. Only after CI is green, create tag git tag -a v0.0.X -m "v0.0.X" git push origin v0.0.X # 6. Verify release workflow completes gh run list --limit 2
Red Flags - STOP
If you catch yourself doing any of these, STOP:
- •Tagging before CI completes
- •"CI will probably pass"
- •"I'll fix it if it fails"
- •Deleting and recreating tags
- •Force-pushing tags
- •"It's just a minor release"
All of these mean: Wait for CI. Fix issues. Then tag.
Common Failures
| Symptom | Cause | Fix |
|---|---|---|
couldn't find main file | Wrong path in goreleaser | Set main: . if main.go at root |
no such tool "covdata" | Package without tests | Add placeholder test to each package |
| Release succeeds but brew fails | Wrong description/homepage | Update goreleaser brews section |
| Had to retag | Tagged before CI passed | WAIT FOR GREEN CI |
| 504 errors in CI | GitHub infra issue | Retry workflow, not a code problem |
Version Bumping
When incrementing version:
- •Patch (0.0.X): Bug fixes, no new features
- •Minor (0.X.0): New features, backwards compatible
- •Major (X.0.0): Breaking changes
Search codebase for version references:
grep -r "version" . --include="*.go" --include="*.yml" --include="*.json" | grep -v test | grep -v vendor
Post-Release Verification
After tag is pushed:
# Watch release workflow gh run watch # Verify release assets gh release view v0.0.X # Test installation (if applicable) go install github.com/user/repo@v0.0.X
The Bottom Line
Retagging is a symptom of rushing.
The 5 minutes spent on pre-flight checks saves 30 minutes of retag cycles and preserves your release history integrity.
Every tag should be final. If you're deleting tags, you skipped the checklist.