This document describes how maintainers cut a release. It reflects the actual automation in the repo, not an aspirational process.
| Concern | Tool | Config |
|---|---|---|
| Versioning | SemVer git tags vX.Y.Z |
— |
| Changelog | changie | .changie.yaml, changes/ |
| Build & publish | GoReleaser | .goreleaser.yaml |
| Release trigger | GitHub Actions on tag push | .github/workflows/release.yaml |
changie is the source of truth for release notes. Its per-version file
(changes/vX.Y.Z.md) is what GoReleaser publishes as the GitHub release body.
changiegoreleaser(only for local dry-runs)- Push access to tags on
skyoo2003/acor
The release workflow itself needs no manual secrets — it uses the built-in
GITHUB_TOKEN for both the GitHub release and pushing images to GHCR.
Run every command below from the repo root. The v prefix is mandatory and
must match across the board: changie batch vX.Y.Z writes changes/vX.Y.Z.md,
and the tag build guards on exactly changes/<tag>.md — so the changie version
and the git tag have to be the identical vX.Y.Z string.
Each change that should appear in the changelog gets a fragment:
changie newThis prompts for a kind (Added, Changed, Deprecated, Removed, Fixed,
Security, Documentation), a body line, and the issue number. It writes a
YAML fragment under changes/unreleased/. Commit it with your change.
The body is one sentence, two at the very most — the second only when it
carries a consequence for the reader: a migration step, a measured number, or
what deliberately did not change. These fragments become the GitHub release
body verbatim, so they are read by people deciding whether to upgrade, not by
people reviewing the diff; the investigation and the file list stay in the pull
request. .changie.yaml enforces the shape with block: false and
maxLength: 400, and CONTRIBUTING.md shows examples.
-
Confirm
mainis green and holds everything you want to ship. -
Pick the version (
vX.Y.Z, SemVer). Base the bump on the kinds of the unreleased fragments inchanges/unreleased/— the major-version call is always yours.Then have the toolchain check the choice against the previous release:
go run golang.org/x/exp/cmd/gorelease@v0.0.0-20260727155853-b88d891fe743 -base=vPREV.Y.Z
goreleasecompares types, not text, and reports the version the changes actually require. It refuses to run on a dirty tree, which is why this is a release step rather than a CI gate — CI usesmake api-checkagainstapi/v1.txtinstead, which works mid-edit and needs no published baseline. Treat a reported incompatible change insidev1as a release blocker: the fix is to restore what was removed, not to bump the version. -
Batch the fragments into a version file:
changie batch vX.Y.Z
This consumes
changes/unreleased/*and writeschanges/vX.Y.Z.md. -
Regenerate the changelog:
changie merge
Rebuilds
CHANGELOG.mdfromchanges/header.tpl.md+ all version files. -
Commit and merge to
mainvia PR:git switch -c release/vX.Y.Z git add CHANGELOG.md changes/ git commit -m "chore: release vX.Y.Z" git push -u origin release/vX.Y.ZOpen the PR, get it green, and merge.
-
Tag and push once merged. Sync
mainfirst so the tag lands on the merge commit, not your local release branch:git switch main && git pull git tag vX.Y.Z git push origin vX.Y.ZThe tag push triggers
.github/workflows/release.yaml.
release.yaml runs on any v* tag and:
- Guards that
changes/<tag>.mdexists — fails fast if you forgotchangie batch/changie merge. - Runs GoReleaser with
--release-notes changes/<tag>.md, which produces:- Binaries for darwin / linux / windows across
386,amd64,arm,arm64(see.goreleaser.yamlfor excluded combos), packaged as.tar.gz(.zipon Windows), each bundlingLICENSE,README.md,CHANGELOG.md,CODE_OF_CONDUCT.md. - A
CHECKSUMSfile (sha256). - Docker images pushed to
ghcr.io/skyoo2003/acor, tagged<tag>-alpine,vMAJOR.MINOR-alpine,vMAJOR-alpine, andlatest-alpine(built fromDockerfile.goreleaser). - A GitHub release named
vX.Y.Zwith the changie notes as its body.
- Binaries for darwin / linux / windows across
Release mode is replace, so re-running the tag build overwrites the existing
release's assets rather than appending.
The v* glob also matches pre-release tags: pushing vX.Y.Z-rc.1 runs the
same pipeline, so it needs its own changes/vX.Y.Z-rc.1.md and would move
latest-alpine onto the RC. This flow assumes final vX.Y.Z tags — don't push
a pre-release tag unless you mean to.
- GitHub release exists with the expected notes and artifacts.
docker pull ghcr.io/skyoo2003/acor:vX.Y.Z-alpineworks.latest-alpinepoints at the new version.
To redo a botched release: fix the fragments/changelog, delete the tag locally
and remotely (git push origin :vX.Y.Z), then re-tag and push. The build
recreates the release in place (replace mode, above).
Watch the Docker tags: deleting the git tag does not remove images already
in GHCR — they stay until a build overwrites them. And latest-alpine (plus
vMAJOR-alpine / vMAJOR.MINOR-alpine) always points at whichever tag built
last, so re-running an older tag's build silently drags latest-alpine back
to that older version. When redoing an older release, re-tag the newest one last.
v1.0.0-v1.4.0 were published from tags that no longer exist here. Deleting a
tag does not unpublish a module version — proxy.golang.org caches it forever —
so they stayed resolvable, and go get github.com/skyoo2003/acor picked
v1.4.0 over the v0.x line. They are retracted in the root go.mod:
retract [v1.0.0, v1.4.0]v1.5.0 is the first supported v1 release. That is why the numbering jumps
from v0.11.x: everything below v1.5.0 in the v1 line is retracted, and the range
stops at v1.4.0 so it can never cover a real release.
There is no separate step to publish the retractions — they take effect with the
release whose go.mod carries them. Two rules when editing this:
- Keep the block on
main. The go command reads retractions only from the highest published version'sgo.mod, so the next release silently un-retracts the old versions if the block is missing. CI enforces this — theVerify retraction blockstep inci.yamlfails if the line is gone, so the rule no longer depends on anyone remembering it. - Only the first comment line reaches users. The go command truncates a retraction rationale at the first newline, so keep that line a complete, actionable sentence.
After tagging a release that carries the block, verify from a scratch module
outside this repo, with vX.Y.Z standing for the tag just pushed:
curl -s https://proxy.golang.org/github.com/skyoo2003/acor/@v/vX.Y.Z.info
go list -m github.com/skyoo2003/acor@latest # want vX.Y.Z
go list -m -versions github.com/skyoo2003/acor # v1.0.0-v1.4.0 must be goneThe curl is needed because the proxy has to fetch the release before its
retractions apply. Do not add -retracted: it lists retracted versions alongside
the rest, so the output is the same before and after.