Releasing
Green Goods ships once a month, at the start of the month, in step with our two-week cycles (two cycles per release). This is the high-level runbook; branch and commit conventions live in CLAUDE.md § Git Workflow.
The monthly rhythm
Work lands on develop (staging) all month and is QA'd there as it goes. At the beginning of each month, once staging is green, we turn what's on develop into a release:
- Cut a
release/<month>-<version>branch fromdevelop— this captures the tested snapshot whiledevelopkeeps moving. - Finalize on the branch — version bump, changelog entry, a last run of the gates.
- Ship — tag
vX.Y.0, merge intomain(production); a GitHub Release is generated automatically. - Back-merge
mainintodevelopso the release returns to staging. - Announce to gardeners.
The release-prep routine opens each cycle with a readiness brief so you cut with a clear picture of what's shipping.
Versioning & naming
- Semver, minor per month (
1.1.0→1.2.0); patch for hotfixes (1.2.1); major for breaking changes.bun run version:bump <x.y.z>keeps all packages on one version and updates the supported release inSECURITY.md. - A release is named for the month it ships in — branch
release/july-2026-v1.2.0, tagv1.2.0, GitHub Release July 2026 — v1.2.0.
GitHub Actions supply-chain policy
Every external action and reusable workflow reference must use a full 40-character commit SHA. Tags such as @v6, branches such as @main, and abbreviated SHAs are rejected by the repository's Actions policy. When updating an action, review the upstream change and pin the exact commit that provides it.
Cutting a release
git switch -c release/july-2026-v1.2.0 origin/develop # cut the tested snapshot
bun run version:bump 1.2.0 # unify packages + SECURITY.md
bun run version:check 1.2.0 # fail if any release marker drifted
# add the curated changelog entry, then run the gates:
bun run format:check && bun run lint && bun run test && bun run build
git commit -am "chore(release): v1.2.0 (July 2026)" && git push -u origin release/july-2026-v1.2.0
gh pr create --base main --title "Release July 2026 — v1.2.0"
# once CI is green and the PR merges, tag the merged main HEAD:
git switch main && git pull && git tag -a v1.2.0 -m "July 2026" && git push origin v1.2.0
Pushing the tag triggers the GitHub Release (auto-generated notes). Then back-merge.
Hotfixes
A production bug mid-month branches from main (not develop), bumps a patch (bun run version:bump 1.2.1), and ships the same way with tag v1.2.1. Then back-merge — this is exactly the step that, when skipped, strands a fix on main.
Back-merge — every time
After every release and every hotfix, merge main back into develop. Use the direct-push fast-path — as a maintainer you push straight to develop:
git fetch origin # refresh origin/main after the release merged
git switch develop && git pull && git merge origin/main && git push
This keeps main an ancestor of develop — the check that it worked is an empty git log origin/develop..origin/main.
mainThe repo has delete_branch_on_merge: true, so GitHub deletes a PR's head branch when the PR merges. A back-merge PR with main as the head therefore deletes main on merge — the org-admin bypass sails straight past main's restrict-deletions rule. This is exactly what deleted main during this release's back-merge; it had to be restored from the reflog.
If the direct push above is somehow unavailable and you must go through a PR, push main to a throwaway branch and PR that into develop — never main itself:
DATE=$(date +%Y-%m-%d)
git fetch origin # copy the current main, not a stale local ref
git push origin origin/main:refs/heads/backmerge/main-$DATE
gh pr create --repo greenpill-dev-guild/green-goods \
--base develop --head "backmerge/main-$DATE" \
--title "Back-merge main → develop"
The changelog
GitHub Releases is the canonical, auto-generated changelog. The docs Changelog is a short, curated summary for gardeners — not a full mirror.