Release Management — Design#
The behaviour in the spec is delivered by a shared reusable release
workflow. A repository opts in with a short caller workflow and a small
.github/release.config.yml; everything else has a sensible default, so a
minimal caller plus a one-line path filter is enough to adopt it.
Branching model#
A release branch is any branch configured as a release target, each with a
release type — stable or prerelease.
- Single branch (zero-config). One release branch (the default branch) produces stable releases. Prereleases are opt-in via a PR label.
- Multi-branch.
dev(prerelease) collects PRs and publishes a prerelease on every merge;main(stable) receivesdev. Mergingdev → maincomputes the stable version from the latest stable release plus the merge PR's bump label — the prerelease counter does not carry over. - One production authority. At most one branch is
release-type: stable; every other release branch isprerelease. The single stable branch (typicallymain) owns the production version — a prerelease branch can never cut a stable release. - Bundled releases. A staging branch collects feature PRs; merging it to a release branch produces exactly one release for all bundled changes.
# .github/release.config.yml
release-branches:
- branch: main
release-type: stable
- branch: dev
release-type: prerelease
Version computation#
- The bump comes from the PR label (
Major/Minor/Patch/NoRelease), defaulting toPatch. Multiple SemVer labels, or a SemVer label withNoRelease, are rejected. Forworkflow_dispatch, the bump is an input. - First release starts from a baseline (
v0.1.0orv1.0.0). Pre-1.0.0breaking changes areMinorper SemVer §4;Majoris never auto-detected pre-1.0.0. - The tag is created on the commit now at the head of the release branch — squash, merge-commit, and rebase strategies alike.
Prereleases#
- Branch-level — a prerelease-type branch publishes on every push, using the
branch name as the identifier:
v1.3.0-dev.1,v1.3.0-dev.2, … - PR-level — a prerelease label on an open PR publishes
v<base>-<identifier>.<counter>:baseis the next version from the PR's bump label,identifieris the normalised branch name, andcounterauto-increments per push. - Artifact-specific conventions replace the SemVer suffix where they exist
(
-alpha.Nfor npm,.devNfor Python). Release candidates use-rc.N, auto-incrementing. - Cleanup deletes prerelease tags, releases, and artifacts after the PR closes (configurable); stable releases are never touched.
Path filtering#
.github/release.config.yml declares release-paths as ordered include/exclude
globs (excludes win). The workflow always runs so validation executes on
every merge; only the release step is skipped when no artifact-affecting path
changed.
release-paths:
- "src/**"
- "Dockerfile"
- "action.yml"
- "!docs/**" # documentation-only changes never release
- "!.github/**" # CI/CD changes never release
Release notes#
The GitHub Release name is the version; the body depends on the trigger:
# <PR title> + description (merged PR), # <first commit line> + remainder
(direct push), or # <summary> + collected history (dispatch). The same note is
handed to Downstream Release Propagation.
Release output#
- A git tag
vX.Y.Zon the release-branch commit — always. - The published artifact where one lives outside git — a container image
(
<image>:<version>and@<digest>), a package in its registry. For Action, workflow, and module artifacts the tag itself is the artifact. - A GitHub Release whose name is the version, carrying the note and the immutable reference (digest, package version, or the tag).
Serialised releases#
Release runs for the same ref are serialised and queue rather than
cancel — an in-flight release is never aborted mid-write, since it may be
part-way through creating a tag or pushing an artifact. The shared workflow
declares a concurrency group keyed by workflow and ref, with
cancel-in-progress disabled:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
Serialisation is provided once by the reusable workflow so every repository inherits it; the mechanism is the GitHub Actions standard. The single-stable-branch rule above is what keeps the production version under one authority — the stable branch is the only ref that ever cuts a production release, and its runs are serialised like any other.
Configuration surface#
| Surface | Where |
|---|---|
| Release branches + type | .github/release.config.yml |
| Bump label / prerelease / RC | PR label, or workflow_dispatch input |
| Path filter | .github/release.config.yml |
| Prerelease cleanup toggle | release config / workflow input |
| Publish target | reusable-workflow input + GitHub environment |
Where this connects#
- Spec — the requirements this design delivers.
- Downstream Release Propagation — consumes the release note and immutable reference.
- GitHub Actions — how the workflow itself is authored (SHA pins, least privilege, concurrency).
- Security — why consumers pin to immutable references.