Releases
How agent-of-empires ships. Maintainer-facing reference for the weekly automated cadence and the manual emergency-release path.
Cadence
- Baseline: at least one release per week.
- A GitHub Action opens a release-staging PR every Wednesday at 09:00 UTC (see
.github/workflows/open-release-pr.yml). - Default semver bump is patch. The maintainer reviews the PR, optionally edits the version bump on the branch (patch -> minor / major), then clicks merge.
- Merging the PR fires
.github/workflows/tag-release-pr.yml, which tags the merge commit. The tag push triggers.github/workflows/release.yml, which builds the four platform binaries and publishes the GitHub release + ClawHub artifact.
The staging PR body still embeds a plain newest-first commit list for the maintainer’s review. The user-facing CHANGELOG.md and GitHub Release body are now generated separately by git-cliff, grouped by conventional-commit prefix; see cliff.toml and the “Changelog visibility” section of CONTRIBUTING.md. Folding the staging PR body into the same grouped view remains on the to-do list under #1387.
The maintainer’s only manual step on a normal release is: review the PR, optionally edit the version bump, click merge.
Weekly release-staging PR
The cron runs every Wednesday at 09:00 UTC. You can also trigger it manually:
gh workflow run open-release-pr.yml # default: patch bump
gh workflow run open-release-pr.yml -f bump=minor
gh workflow run open-release-pr.yml -f bump=major
The workflow:
- Reads the current version from
Cargo.toml. - Computes the next version based on the bump (defaults to patch).
- Refuses to run if the tag or the staging branch already exists.
- Bumps
Cargo.toml+Cargo.lockon a new branchrelease-staging/vX.Y.Zviacargo set-version+cargo generate-lockfile. - Regenerates
CHANGELOG.mdviagit cliff --tag vX.Y.Zand stages it in the same release commit so the staging PR shows the changelog diff for review. - Dumps every commit since the last
v*tag (newest first,--no-merges, with author + short SHA + date) into the PR body as a separate maintainer-review summary. Folding that summary into the same prefix-grouped git-cliff render is queued under #1387. - Opens the PR labeled
release-staging, with a<!-- release-version: X.Y.Z -->marker in the body.
Adjusting the bump in-flight
If the auto-staged version is wrong (e.g., the diff contains a breaking change but the workflow picked patch), edit Cargo.toml + Cargo.lock on the staging branch and update the marker in the PR body to match. Re-run git cliff --config cliff.toml --tag "vX.Y.Z" --output CHANGELOG.md on the branch so the changelog header reflects the corrected version. The post-merge tagger reads the version from Cargo.toml at the merge commit and cross-checks it against the marker; if they disagree the tag step refuses to run.
Post-merge tagging
.github/workflows/tag-release-pr.yml listens for merged PRs labeled release-staging on main. It:
- Checks out
github.event.pull_request.merge_commit_sha. This is the exact commit GitHub produced from the merge; it is immutable and anchored regardless of what lands onmainafterwards. - Reads the version from
Cargo.toml, sanity-checksCargo.lockagrees, and verifies the PR body marker matches. - Refuses to tag if
vX.Y.Zalready exists. - Warns (does not fail) if
mainadvanced after the merge. - Pushes an annotated
vX.Y.Ztag pointing atmerge_commit_shausingRELEASE_TOKENso the tag push triggers downstream workflows (GITHUB_TOKENdoes not).
The tag push fires release.yml exactly as it does today.
Why we tag the merge SHA, not main
Between the moment the staging PR merges and the moment the tag push completes, other PRs can land on main. If we tagged origin/main, the tag would point at a commit whose Cargo.toml no longer matches the version, and release.yml’s validate step would fail. Tagging merge_commit_sha dissolves that race.
Emergency releases
The original .github/workflows/prepare-release.yml is still wired up for emergencies. It is a manual workflow_dispatch that takes a version, bumps Cargo.toml + Cargo.lock, and pushes the tag directly to main. Use it when:
- A critical fix needs to ship before the next Wednesday.
- The weekly workflow is broken for some reason and you need to cut a release by hand.
gh workflow run prepare-release.yml -f version=1.7.2
This bypasses the staging PR. prepare-release.yml regenerates CHANGELOG.md from git-cliff and folds it into the same chore: bump version commit; release.yml builds the binaries and creates the GitHub Release with the per-version body that git-cliff emits via --current --strip header.
Versioning
We follow semver, but the autopick is patch. Maintainer adjusts when:
- Major: breaking config changes (e.g., the
update_check_modecutover in #1140, even though we ship a migration), removed CLI subcommands, on-disk format breakage that needs maintainer attention beyond a migration. - Minor: new user-visible features, new CLI subcommands, new config sections, anything covered by a
feat:commit since the last release. - Patch: bug fixes, refactors, docs, perf, internal CI / tests.
If you are uncertain, the safer call is the bigger bump.
Out of scope
The staging PR body’s plain newest-first commit list is still intended for maintainer review only. Folding that body into the same git-cliff grouped render that drives CHANGELOG.md and the Release body is queued under #1387.