Complete guide to GitOps Reverser's automated release system with semantic versioning.
Required: Enable GitHub Actions to create pull requests.
Your organization has disabled this at the org level. Enable it in this order:
-
Organization Settings (requires admin/owner permissions):
- Go to:
https://github.com/organizations/YOUR_ORG/settings/actions - Under "Workflow permissions": ✅ "Allow GitHub Actions to create and approve pull requests"
- Click Save
- Go to:
-
Repository Settings (should now be available):
- Go to:
https://github.com/YOUR_ORG/gitops-reverser/settings/actions - Under "Workflow permissions": ✅ "Allow GitHub Actions to create and approve pull requests"
- Click Save
- Go to:
# Create a test commit
git commit --allow-empty -m "feat: test automated releases"
git push origin mainExpected outcome:
- CI runs (lint, unit tests, e2e)
- If tests pass → Release PR created for v0.2.0
- Merge PR → GitHub Release + Docker images published
Push to main
↓
[CI: Build & Test + E2E]
↓ (tests pass)
[release-please analyzes commits]
↓ (version bump needed)
[Create/Update Release PR]
↓ (human reviews & merges)
[Create GitHub Release + Tag (draft)]
↓
[Retag CI-built images to semver + latest, sign, attest → publish the release]
The next Release PR is opened/refreshed by a second release-please pass that runs after a release is published, so it never sees the still-draft release. See What Happens When You Push.
On every push to main:
-
CI Tests Run (
.github/workflows/release.ymlcalls the reusable.github/workflows/ci.yml— the same jobs every PR runs):- Lint:
task lint(golangci-lint, hadolint, actionlint, helm lint) - Unit tests:
task test(with the coverage ratchet) - E2E tests: the Ginkgo suite in a k3d cluster, plus the project image scan
- Lint:
-
Release handling (if tests pass) — release-please runs as two separate passes:
- Cut pass (
release-pleasejob): if the previous Release PR was merged, cut its GitHub Release (created as a draft — see step 4). This pass usesskip-github-pull-request, so it does not open the next PR. - PR pass (
release-please-prjob): analyze commits since the last release, determine the version bump, and create/update the next Release PR (skip-github-release). It runs after the release is published, so it sees the just-published release instead of the draft. Without this split, the run that cut a draft would open a PR computed against the whole history and propose a bogus "release everything" version.
- Cut pass (
-
Release PR Contents:
- Auto-generated CHANGELOG.md updates
- Updated
charts/gitops-reverser/Chart.yamlversions - Summary of all changes
-
When Release PR is Merged:
- GitHub Release created with tag (e.g.,
v0.2.0) — first as a draft, so every signed asset (crds.yaml,install.yaml, SBOM,.sigstore.jsonsignatures,.intoto.jsonlattestations) can be attached before it goes public; immutable releases reject post-publish uploads, sopublish-releaseflips the draft to published only after every asset is in place. - The linux/amd64 + linux/arm64 image digests already built and scanned by that
commit's CI run are retagged (not rebuilt) as
0.2.0,0.2,0,latestonghcr.io, then cosign-signed with SLSA provenance + SPDX SBOM attestations
- GitHub Release created with tag (e.g.,
<type>(<optional scope>): <description>
[optional body]
[optional footer(s)]
| Type | Version Bump | Example |
|---|---|---|
feat |
Minor (0.1.0 → 0.2.0) | feat(controller): add multi-repo support |
fix |
Patch (0.1.0 → 0.1.1) | fix(webhook): handle timeout |
feat! or BREAKING CHANGE: |
Major (0.1.0 → 1.0.0) | feat!: redesign API |
docs |
No bump | docs: update README |
style |
No bump | style: format code |
refactor |
No bump | refactor: simplify logic |
perf |
Patch (0.1.0 → 0.1.1) | perf: optimize loop |
test |
No bump | test: add unit tests |
build |
No bump | build: update deps |
ci |
No bump | ci: improve workflow |
chore |
No bump | chore: update .gitignore |
revert |
Patch (0.1.0 → 0.1.1) | revert: undo feature X |
Feature (Minor Bump):
git commit -m "feat(controller): add multi-repository support
Allows configuring different Git repos for different namespaces,
improving flexibility in audit trail organization.
Closes #42"Bug Fix (Patch Bump):
git commit -m "fix(webhook): prevent race condition in event queue
The event queue could process events out of order. This adds proper locking.
Fixes #123"Breaking Change (Major Bump):
git commit -m "feat!: redesign GitRepoConfig API
BREAKING CHANGE: The GitRepoConfig CRD uses a different schema.
Users must migrate using the provided script.
Migration guide: docs/migration-v1.md"Solution: Enable at org level first (if greyed out), then repo level. See Prerequisites Setup above.
Check:
- ✅ CI tests passed (green checkmark in Actions tab)
- ✅ Commit used conventional format (
feat:,fix:, etc.) - ✅ Pushed to
mainbranch - ✅ GitHub Actions has PR creation permissions
Retry:
# Check workflow status
gh run list --branch main --limit 5
# Re-run failed workflow
gh run rerun <run-id>
# Or push new commit
git commit --allow-empty -m "feat: trigger release"
git push origin mainRelease process won't proceed if tests fail. Fix tests first:
# Run tests locally
make lint
make test
make test-e2e
# Fix issues, then push
git add .
git commit -m "fix: resolve test failures"
git push origin mainCauses:
- Multiple commit types (most significant wins)
!orBREAKING CHANGE:triggers major bump.release-please-manifest.jsonout of sync
Solution: Edit Release PR version or close it and retrigger.
Symptom: the release PR proposes an unexpectedly large bump and a changelog that re-announces releases that already shipped. The Release Please job log names the cause:
✔ No latest release found for path: ., component: , but a previous version (0.40.0)
was specified in the manifest.
Cause: release-please anchors on published GitHub Releases and falls back to git
tags. release-please-config.json sets draft: true, which the publish jobs need so
they can attach assets before immutable releases freeze the release. So a publish job
that fails leaves the release as a draft, and a draft is skipped by the release
search and creates no tag. .release-please-manifest.json still names the version,
leaving release-please with a version it cannot anchor, so it collects every commit in
the repository.
Do not merge that PR: it burns a version number on a changelog of already-released work.
Solution: supply the missing anchor. A bare lightweight tag is enough: release-please falls back to tags, so no GitHub Release is required.
# The SHA is the "chore(main): release X.Y.Z" merge commit for the stranded version.
git tag vX.Y.Z <release-commit-sha>
git push origin vX.Y.Z
# Close the oversized PR and drop its branch so a fresh one is generated, not amended.
gh pr close <pr-number> --delete-branch
# Re-run the Release Please job, then confirm the new PR is the expected patch bump.
gh run rerun <run-id> --job <job-id>Then resolve the stranded draft. Delete it with gh release delete vX.Y.Z --yes (which
leaves the tag in place) when the assets it is missing should ship in the next release.
Publishing it instead freezes a release whose own notes link to assets it does not carry.
last-release-sha does not rescue this: release-please reads it only as a top-level key,
and this repo's copy sits under packages["."], where it is ignored.
Symptom: a feat or fix merged since the last release has no changelog line, and none
of its breaking changes appear. The Release Please job log says nothing at its default level.
Cause: release-please parses each commit with @conventional-commits/parser and, when the
parse throws, drops the whole commit with only a debug log. A body line that starts with a word
followed by nested parentheses, such as `^([0-9]+(\.[0-9]+)? or f(a(b)), reads as an
unclosed type(scope token and throws. The squash body carries every commit message on the
branch, so one such line in any commit is enough. This dropped #388 from 0.50.0.
Prevention: the "Squash message parses" check in pr-title.yml rebuilds the squash message
and parses it before merge. Fix a failure by indenting the named line, or starting it with other
text, in the commit that holds it. To check a branch locally:
(cd hack/release-notes && npm ci --ignore-scripts)
hack/release-notes/squash-message.sh origin/main HEAD "<PR title>" | node hack/release-notes/check.mjssquash-message.sh builds the message the way GitHub does for this repository: a single-commit PR
squashes to that commit's own message, and the PR title is not used; several commits squash to the
PR title over the bulleted commit messages. hack/release-notes/test.sh covers both shapes.
Repair after a release: add the missing entries to that release's section of CHANGELOG.md
by hand (release-please only prepends, so it never rewrites them) and edit the GitHub Release
notes to match.
Check:
- Build logs in Actions tab
- Multi-arch build issues (amd64/arm64)
- Registry authentication (GITHUB_TOKEN permissions)
# View logs
gh run view --log
# Manually retrigger
gh run rerun <run-id>✅ Good:
feat(controller): add support for custom branch names
Users can now specify different branch names for different GitRepoConfigs,
allowing more flexible repository organization.❌ Bad:
add stuff
fix bug
wipIndicate which component is affected:
feat(controller): ...fix(webhook): ...docs(readme): ...test(integration): ...
git commit -m "feat(controller): add multi-repo support"
git commit -m "docs: document multi-repo config"
git commit -m "test: add multi-repo tests"Before merging:
- ✅ Verify changelog accuracy
- ✅ Check version bump is appropriate
- ✅ Ensure all commits are included
- ✅ Confirm breaking changes are documented
For major version bumps:
- Discuss in an issue first
- Add migration guide to
docs/ - Update relevant documentation
- Announce in discussions/Slack
These are automatically updated by release-please:
.release-please-manifest.json- Current versioncharts/gitops-reverser/Chart.yaml- Helm chart versionsCHANGELOG.md- Auto-generated changelog
Don't modify these without understanding the impact:
release-please-config.json- Semantic versioning rules.github/workflows/ci.yml- Validation pipeline (PRs + reused on main).github/workflows/release.yml- Release/publish pipeline (push to main)
See docs/ci-overview.md for the full CI/CD design.
If automation fails completely:
# 1. Tag the commit
git tag -a v0.2.1 -m "Release v0.2.1"
git push origin v0.2.1
# 2. CI will build and push Docker images
# 3. Create GitHub Release
gh release create v0.2.1 \
--title "v0.2.1" \
--notes "Emergency release: fix critical bug"Note: Manual releases should be rare. Fix the automation instead.
- Conventional Commits Specification
- Semantic Versioning
- Release Please Documentation
CONTRIBUTING.md- Full commit guidelinesREADME.md- Quick reference
- Check this document first
- Review existing Release PRs for examples
- Check GitHub Actions logs for errors
- Open an issue or discussion if stuck