Release Process

February 28, 2026 · View on GitHub

This document describes the automated release process for floop using GoReleaser.

Overview

The release pipeline is fully automated with semantic version bumping:

  1. Code merges to main trigger automatic version detection from commit messages
  2. Commits are parsed for conventional commit prefixes: feat: → minor, fix:/perf: → patch, breaking changes → major
  3. Chore-only merges (docs, ci, test, refactor, etc.) skip the release entirely
  4. GoReleaser builds binaries, publishes to GitHub Releases, and updates the Homebrew tap

Installation

Homebrew (macOS/Linux)

brew install nvandessel/tap/floop

Go

go install github.com/nvandessel/floop/cmd/floop@latest

Manual

Download binaries from GitHub Releases, extract, and add to your PATH.

Auto-Release

Every push to main that changes code files triggers auto-release.yml. Documentation-only changes (.md, docs/, .beads/, .floop/, LICENSE) and workflow changes (.github/) are ignored via paths-ignore. CI (Test, Lint, Build) is enforced as a required status check via GitHub rulesets, so it must pass before merge.

Semantic Version Bumping

Since the repo uses squash-merge, PR titles become commit messages. The pr-title.yml workflow enforces conventional commit format on PR titles. Version bumping uses svu (by the GoReleaser maintainer) to parse commits since the last tag and determine the bump type:

Commit TypeBumpExample
feat!: or BREAKING CHANGE: in bodymajorfeat!: remove legacy API
feat:minorfeat: add --tags flag
fix:, perf:patchfix: handle empty input
chore:, ci:, test:, docs:, build:, style:, refactor:skipchore: update deps

Skip Mechanisms

MethodUse Case
github-actions[bot] actorAutomatic — prevents version-bump commits from re-triggering
[skip release] in commit messageBatch multiple PRs before releasing
skip-release label on PRMark a PR as not release-worthy before merging

Example: Batching Changes

When you want to merge several PRs before cutting a release:

  1. Add the skip-release label to each PR (or include [skip release] in merge commits)
  2. Merge the PRs
  3. On the last PR, remove the label (or omit the skip marker) — the auto-release triggers

Release Artifacts

Each release produces:

ArtifactDescription
6 binarieslinux/darwin/windows × amd64/arm64
Archives.tar.gz for Unix, .zip for Windows
Checksumschecksums.txt with SHA256 hashes
Release notesAuto-generated by GoReleaser from conventional commits

Archives include:

  • floop binary
  • LICENSE
  • README.md
  • docs/ directory

Local Testing

Test the release process locally before pushing:

# Install tools (if not already installed)
go install github.com/goreleaser/goreleaser/v2@v2.14.1
go install github.com/caarlos0/svu/v3@latest

# Preview what svu would do
svu current   # show current version
svu next      # show next version based on commits

# Validate configuration
goreleaser check

# Test build without publishing
goreleaser build --snapshot --clean

# Check built binaries
ls -lh dist/*/

# Test version injection
./dist/floop_linux_amd64_v1/floop --version

# Test full release pipeline (doesn't publish)
goreleaser release --snapshot --clean

# Clean up
rm -rf dist/

Version Information

All binaries include build metadata:

./floop --version
# Output: floop version v0.2.0 (commit: abc1234, built: 2026-02-10T15:30:00Z)

./floop version --json
# Output: {"version":"v0.2.0","commit":"abc1234","date":"2026-02-10T15:30:00Z"}

Version sources:

  • version — From git tag (e.g., v0.2.0)
  • commit — Short git commit SHA
  • date — Build timestamp (RFC3339)

Development builds:

  • Built with make build show version=dev
  • Include current commit SHA and build time

Troubleshooting

Release Workflow Fails

Check GoReleaser logs:

gh run view --log

Common issues:

  • Missing tag: Ensure version bump workflow completed
  • Build failure: Check go.mod and dependencies are up to date
  • Invalid config: Run goreleaser check locally

Wrong Version Tagged

If you need to delete and recreate a tag:

# Delete local tag
git tag -d v0.2.0

# Delete remote tag (careful!)
git push origin :refs/tags/v0.2.0

# Create new tag
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0

Note: Only do this immediately after tagging, before anyone downloads the release.

Release Published but Broken

If a release is published but has issues:

  1. Don't delete the release — it breaks links
  2. Create a hotfix and release a new patch version
  3. Update the broken release description with a warning and link to the fix

Emergency Hotfix

For critical bugs in production:

# Create hotfix branch from the release tag
git checkout -b hotfix/critical-bug v0.2.0

# Fix the bug
# ... make changes ...

# Commit fix
git add .
git commit -m "fix: critical bug description"

# Push hotfix branch
git push origin hotfix/critical-bug

# Create PR to main
gh pr create --base main --title "fix: critical bug" --body "Emergency hotfix for v0.2.0"

# After PR merge, auto-release creates the patch automatically

CI/CD Workflows

pr-title.yml

Trigger: Pull request opened, edited, or synchronized Purpose: Enforce conventional commit format on PR titles (since squash-merge uses PR title as commit message)

Steps:

  1. Validate PR title against allowed conventional commit types using amannn/action-semantic-pull-request

auto-release.yml

Trigger: Push to main (code changes only, docs excluded) Purpose: Automatically release with semantic version bumping from commit messages

Jobs:

  1. check-skip — Evaluate skip conditions (bot actor, commit message, PR label)
  2. release — Use svu next to determine version, tag, and run GoReleaser

test-release.yml

Trigger: PR changes to release files Permissions: contents: read Purpose: Validate release config before merge

Steps:

  1. Checkout code
  2. Run GoReleaser in snapshot mode
  3. Verify binaries work
  4. Check for expected builds

Configuration Files

FilePurpose
.goreleaser.ymlGoReleaser configuration (builds, archives, changelog, Homebrew cask)
.github/workflows/pr-title.ymlConventional commit enforcement on PR titles
.github/workflows/auto-release.ymlAuto-release on merge to main (svu + GoReleaser)
.github/workflows/test-release.ymlPR validation workflow
MakefileLocal build with version injection

Homebrew Distribution

Each release automatically pushes a Homebrew cask to nvandessel/homebrew-tap via GoReleaser's homebrew_casks configuration.

How it works:

  1. GoReleaser builds archives for all platforms
  2. The homebrew_casks section creates/updates a cask file in the tap repo
  3. Users install with brew install nvandessel/tap/floop

Requirements:

  • HOMEBREW_TAP_GITHUB_TOKEN secret on the floop repo (PAT with repo scope to push to homebrew-tap)
  • The nvandessel/homebrew-tap repository must exist

Future Enhancements

Not currently implemented but can be added later:

  • Docker images — Multi-arch container publishing
  • Scoop manifest — Windows package manager
  • AUR package — Arch Linux user repository
  • Binary signing — GPG or cosign signatures
  • SBOM generation — Software bill of materials
  • Release channels — Beta/RC releases

To add these, extend .goreleaser.yml with the appropriate sections. See GoReleaser documentation for details.

Questions?

For questions about the release process:

  • Check the GoReleaser docs
  • Review past releases: gh release list
  • Open an issue with the question label