Cutting release branches from trunk Jump to heading

Pure trunk-based development deploys trunk directly. Many teams cannot: mobile apps go through store review, installed software ships on a schedule, enterprise customers need a stable build for a week of acceptance testing. Trunk-based development handles this with release branches that are deliberately limited: cut from trunk as late as possible, never developed on, receiving only fixes cherry-picked from trunk, and deleted or locked once the release is done. They exist so trunk can keep moving while a release stabilises, not as a second place where work happens. This page sets that pattern up and enforces its limits, within trunk-based development setup.

When to use this approach Jump to heading

  • You practise trunk-based development but cannot deploy trunk continuously to every target.
  • A release needs a stabilisation period while trunk continues to change.
  • You ship to app stores, on-premise customers, or on a scheduled release train.
  • If you deploy every merge to production, you probably need no release branches; see running releases with GitHub Flow.

Step 1 โ€” Cut the branch as late as possible Jump to heading

Cut the release branch from a green trunk commit just before stabilisation starts. Cutting early means more fixes to cherry-pick and more time for the branch and trunk to drift.

git fetch origin
# Choose the latest trunk commit with a green pipeline
sha=$(gh run list --branch main --workflow ci.yml --status success --limit 1 --json headSha --jq '.[0].headSha')
git switch -c release/2026.10 "$sha"
git push -u origin release/2026.10
git tag -a v2026.10.0-rc.1 -m "Release candidate 1" && git push origin v2026.10.0-rc.1
A release branch cut late from trunkTrunk keeps moving with daily merges. The release branch is cut from a green trunk commit just before stabilisation. Fixes are made on trunk first and cherry-picked to the release branch, which is tagged for each candidate and the final release, then locked.trunk moves on; the release branch only receives fixesmain (trunk)t1t2fixt3release/2026.10rc.1fix'finalno commit is ever made on the release branch that is not first on trunk A release branch cut late from trunkTrunk keeps moving with daily merges. The release branch is cut from a green trunk commit just before stabilisation. Fixes are made on trunk first and cherry-picked to the release branch, which is tagged for each candidate and the final release, then locked.trunk moves on; the release branch only receives fixesmain (trunk)t1t2fixt3release/2026.10rc.1fix'finalno commit is ever made on the release branch that is not first on trunk

Step 2 โ€” Fix on trunk first, then cherry-pick Jump to heading

Every fix for the release goes into trunk through a normal pull request, then is cherry-picked to the release branch. This guarantees the next release, cut from trunk, already contains it.

# After the fix merges to main as abc1234
git switch release/2026.10 && git pull --ff-only
git cherry-pick -x abc1234
git push

Recording the source of each cherry-pick is covered in recording backports with cherry-pick -x, and automation with labels in automating backport pull requests with labels.

Step 3 โ€” Enforce โ€œno development on the release branchโ€ Jump to heading

The rule that makes this work is also the easiest to break: someone needs a quick change for the release and commits it to the release branch directly. Enforce that every commit on a release branch carries a cherry-pick reference to trunk.

# CI on pull requests targeting release/*: every commit must be a recorded cherry-pick from main
base=$(git merge-base "origin/$BASE_REF" HEAD)
for c in $(git rev-list --no-merges "$base..HEAD"); do
  src=$(git log -1 --format=%B "$c" | sed -n 's/.*cherry picked from commit \([0-9a-f]*\).*/\1/p')
  if [ -z "$src" ] || ! git merge-base --is-ancestor "$src" origin/main; then
    echo "::error::$(git log -1 --format='%h %s' "$c") is not a cherry-pick of a commit on main"; exit 1
  fi
done
Can this change go onto the release branch?If the change is a fix already merged on trunk, cherry-pick it with -x. If it is a fix not yet on trunk, land it on trunk first. If it is new functionality, it waits for the next release cut from trunk.What kind of change is it?fix, already on trunkCherry-pick -xallowedfix, not on trunk yetTrunk firstthen cherry-picknew functionalityNext releasenot on this branchthe check in CI makes the first branch the only path that passes Can this change go onto the release branch?If the change is a fix already merged on trunk, cherry-pick it with -x. If it is a fix not yet on trunk, land it on trunk first. If it is new functionality, it waits for the next release cut from trunk.What kind of change is it?fix, already on trunkCherry-pick -xallowedfix, not on trunk yetTrunk firstthen cherry-picknew functionalityNext releasenot on this branchthe check in CI makes the first branch the only path that passes

Step 4 โ€” Tag candidates and the final release on the branch Jump to heading

Each build sent for testing gets a release-candidate tag; the final build gets the release tag. Tags on the release branch record exactly what was shipped, even though the branch itself is temporary.

git tag -s v2026.10.0 -m "Release 2026.10.0" origin/release/2026.10
git push origin v2026.10.0

Tagging conventions for candidates are in tagging pre-releases and release candidates.

A release branch's short lifeThe branch is cut from green trunk on Monday and tagged as the first candidate. Fixes found during testing land on trunk and are cherry-picked through the week. The final tag is created on Friday, the release ships, and the branch is deleted the following week once no patch is expected.Cut + rc.1from green trunkMonFixestrunk first, cherry-pickTueโ€“Thurc.2re-testedThuFinal tagshippedFriBranch deletedtag remainsnext weeka week-long branch drifts very little from trunk A release branch's short lifeThe branch is cut from green trunk on Monday and tagged as the first candidate. Fixes found during testing land on trunk and are cherry-picked through the week. The final tag is created on Friday, the release ships, and the branch is deleted the following week once no patch is expected.Cut + rc.1from green trunkMonFixestrunk first, cherry-pickTueโ€“Thurc.2re-testedThuFinal tagshippedFriBranch deletedtag remainsnext weeka week-long branch drifts very little from trunk

Step 5 โ€” Retire the branch quickly Jump to heading

Once the release ships and no further patch is expected, lock or delete the branch. The tags preserve everything needed to rebuild or patch it later โ€” a patch release can always start a new branch from the release tag.

# Delete the branch; the tag keeps the history reachable
git push origin --delete release/2026.10
# Later, if a patch is needed:
git switch -c release/2026.10 v2026.10.0

If customers need long-term patches, keep the branch within a support window instead, as in supporting multiple maintained versions.

Step 6 โ€” Keep trunk releasable regardless Jump to heading

Release branches can become a crutch: if trunk is often broken, teams cut release branches early to escape it. The cure is to keep trunk green, so a release can be cut from any recent commit.

# How often was main green over the last 50 runs?
gh run list --branch main --workflow ci.yml --limit 50 --json conclusion \
  --jq '(map(select(.conclusion=="success")) | length) as $g | "\($g)/\(length) green"'

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Isnโ€™t a release branch a step away from trunk-based development? Jump to heading

Not if it is short-lived and receives only cherry-picked fixes. Trunk remains the only place where development happens; the release branch is a stable snapshot with a few patches.

What if a fix cannot be cherry-picked cleanly? Jump to heading

Resolve the conflict on the release branch, keeping the cherry-pick reference, and note the adaptation. The analysis is in resolving cherry-pick conflicts on older branches.

Should release branches have their own CI? Jump to heading

Yes โ€” the full release pipeline, including packaging and any long suites. Trunkโ€™s pre-merge pipeline can stay fast because releases are tested thoroughly on their branch.