This document captures the release process. The goal is that running through it mechanically produces a tagged, published release with consistent metadata.
Replace X.Y.Z with the version being released (e.g. 4.1.2). The release
branch is X.Y.Z-rc; the destination branch is master.
- All work for the release is on
X.Y.Z-rc, branched frommaster. masterhas no commits the rc branch is missing:If non-empty, rebase the rc branch ontogit fetch origin git log --oneline HEAD..origin/master # must be emptymasterfirst.- Latest CI run on
X.Y.Z-rcis green:gh run list --branch X.Y.Z-rc --limit 1
Run from the repo root on the rc branch:
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo build --verbose # integration tests need a debug build
cargo testAll four must pass before tagging.
Then verify the release metadata:
| Item | Where | Check |
|---|---|---|
| Workspace version | Cargo.toml ([workspace.package]) |
matches X.Y.Z |
| Lockfile in sync | Cargo.lock |
bear, bear-codegen, etc. show X.Y.Z; clean cargo build does not modify it |
| Man page date | man/bear.1.md line 3 |
set to today (e.g. April 25, 2026) |
| Man page generated | man/bear.1 |
regenerated from .md (see below) |
If the man page date or content changed, regenerate bear.1:
cd man && pandoc -s -t man bear.1.md -o bear.1Commit any pre-flight fixups to X.Y.Z-rc and let CI run again.
Use a fast-forward merge to keep master's first-parent history linear, which
is the convention the 4.x series follows.
git checkout master
git pull --ff-only origin master
git merge --ff-only X.Y.Z-rcDo not create a merge commit. If --ff-only fails, the rc branch is behind
master - rebase it and start over.
Tags are unprefixed (4.1.2, not v4.1.2), annotated, and SSH-signed. The
message body uses the v-prefixed form.
Verify your signing config once:
git config --get gpg.format # ssh
git config --get user.signingkey # path or key starting with "key::"
git config --get tag.gpgsign # true (recommended)Then tag the merge tip:
git tag -s X.Y.Z -m "vX.Y.Z"
git tag --verify X.Y.Z # confirm signature is goodgit push origin master
git push origin X.Y.ZUse prior releases as the template (gh release view 4.1.1). Sections, in
order, when applicable:
### Features### Bug Fixes### Performance### Internal Refactoring### Documentation### Closed Issues- bullet list of#NNN - one-line description### Thanks- issue reporters and external PR authors, by@handle### New Contributors- first-time contributors only
Trailer:
**Full Changelog**: https://github.com/rizsotto/Bear/compare/PREV...X.Y.Z
Useful inputs while drafting:
# commit subjects since the previous tag
git log --pretty="%s" PREV..X.Y.Z
# external contributors since the previous tag
git log --pretty="%an <%ae>" PREV..X.Y.Z | sort -u
# issues closed since the previous tag
gh issue list --state closed --search "closed:>=YYYY-MM-DD" --limit 50
# PRs merged since the previous tag
gh pr list --state merged --search "merged:>=YYYY-MM-DD" --limit 50Save the notes to a temporary file (e.g. /tmp/release-notes.md) - they are
reused for the GitHub release and the discussion announcement.
gh release create X.Y.Z \
--title X.Y.Z \
--notes-file /tmp/release-notes.md \
--verify-tagUse --draft first if you want to review the rendered output before it goes
live, then gh release edit X.Y.Z --draft=false.
The pinned thread for release announcements is #399.
Keep the announcement short:
- One-line summary linking to the release page.
- Three to five highlight bullets.
- Thanks to issue reporters and external contributors by
@handle. - Invite users to open new issues for problems; the discussion thread is for general feedback.
Post with:
gh api graphql -f query='
mutation($id: ID!, $body: String!) {
addDiscussionComment(input: {discussionId: $id, body: $body}) {
comment { url }
}
}' -f id=DISCUSSION_NODE_ID -f body="$(cat /tmp/announcement.md)"(Get DISCUSSION_NODE_ID once via
gh api graphql -f query='{ repository(owner:"rizsotto",name:"Bear") { discussion(number:399) { id } } }'.)
Alternatively, paste through the web UI.
- Pin the announcement comment in discussion #399 if you typically do.
- Verify the release page renders correctly and the tag signature is shown.
- Notify downstream packagers if the release contains packaging-relevant changes (install layout, prerequisites, breaking flags). Past channels: Homebrew, Arch, Debian/Fedora maintainers.
- Open a new branch
<next>-rcwhen work for the next version begins, and bumpCargo.toml([workspace.package].version) on that branch.
| Item | Convention |
|---|---|
| Release branch name | X.Y.Z-rc |
| Tag name | X.Y.Z (no v prefix) |
| Tag type | annotated, SSH-signed |
| Tag message | vX.Y.Z |
| GitHub release title | X.Y.Z |
| Merge style | fast-forward only |
| Version source of truth | Cargo.toml [workspace.package].version |