Release Automation
How a Release Candidate flows from dev to production, covering versioning, release notes, the Linear and QA handover, and the tags, GitHub Releases, and dashboard release notes it produces.
Referenta release notes are generated from the GitHub release process, not from manually edited dashboard content.
Overview
The pipeline runs from dev to main. Feature branches merge into dev,
which is staging: anything on dev is a shippable Release Candidate. The
single pull request from dev into main is that Release Candidate, and
merging it ships production. Every version number, its release notes, and its
QA state are driven from that one PR.
End to end:
- Open the RC PR (
dev→main): the title sets the version, the body is the release notes. - Hand it to QA in Linear by adding the
sync-to-linearlabel. The RC's issues are mirrored into a Linear release at the Staging stage. - QA signs off in Linear, moving each issue through the board.
- Merge into
main: CI cuts the git tag and GitHub Release, and the Linear release is completed. - The dashboard reflects it: the in-app release-notes page reads the new GitHub Release.
The rest of this page details each step.
Trigger
The release automation lives in .github/workflows/ci.yml and only runs when all of the following are true:
- the event is a pull request,
- the pull request is closed,
- the pull request was merged, and
- the base branch is
main.
This means the release flow is tied to the promotion step into production rather than to everyday feature branches.
Title And Description Rules
For the release job to create a version tag, the merged PR title must match one of these formats:
Release Candidate: vX.Y.Z
RC: vX.Y.Z
Release: vX.Y.ZThe version must include the leading v (e.g. v3.3.4, not 3.3.4). A title that looks like a release (starts with Release Candidate:, RC:, or Release:) but does not match <Prefix>: vX.Y.Z fails the check loudly rather than silently producing no tag or release — so a near-miss like a missing v or missing space is caught instead of shipping unnoticed. The same guard gates the Linear sync. A title that is not release-shaped at all is ignored.
Version numbers follow SemVer (MAJOR.MINOR.PATCH): bump the minor for the
features accumulated on dev, the patch for a fix-only release.
The workflow extracts the version directly from the title. The pull request body becomes the GitHub Release description.
Operationally, that makes the PR itself the release handoff artifact:
- the title defines the version number,
- the description defines the public release content, and
- the merge into
maintriggers the publication step.
What The Workflow Creates
When the title matches and the PR is merged into main, the workflow:
- checks out the repo with tag push permissions,
- extracts the version from the PR title,
- creates an annotated git tag on the merge commit if the tag does not already exist, and
- creates a GitHub Release whose title is the version and whose notes come from the PR body.
This keeps releases traceable to the exact merge commit and avoids manually creating production tags outside the CI flow.
Linear Release Sync
Alongside the tag automation, .github/workflows/linear-release.yml mirrors the
Release Candidate into Linear's Releases product (pipeline Referenta App,
stages Staging → Released) so QA can work from the release object.
- The Staging sync is opt-in via the
sync-to-linearlabel. Add that label to the open RC PR (dev→main, title matching the formats above) to start it; the sync then attaches every Linear issue found in the new commit range to the release for that version at the Staging stage, and re-runs on each subsequent title/body edit or push todevwhile the label is present. Without the label, nothing syncs during Staging. Issues are discovered from commit messages, branch names, and squash-merge PR references ((#123)) resolved through the Linear GitHub integration — no special commit format is required. - When the RC PR merges, the workflow runs a final sync and completes the release, landing it on the Released stage — independent of the label, so a release is always recorded on merge (the final sync creates it if the RC was never labeled during Staging). Linear release automations then move the contained issues to their final status.
If a sync or promotion run fails partway (for example between the final sync and completion), re-run the failed job from the Actions tab — steps address the release by version, so a re-run resumes where it failed. If an RC PR is retitled to a different version after syncs ran, archive the stray old release manually in Linear.
Feature-branch PRs into dev need no special handling: their squash commit's
PR reference is resolved to linked issues at sync time. Direct pushes to dev
are only attached to issues when the commit message or branch names a Linear
identifier.
The workflow can be exercised manually via workflow_dispatch (inputs:
version, dry_run, base_ref) from the Actions tab; dry_run defaults
to true for a mutation-free preview — uncheck it to perform a real sync.
The optional base_ref overrides where the commit scan starts (normally
the last release's commit) — useful to backfill a release's issue list from
a known point. It must be a commit on the dev history: a version tag will
not work, because release tags sit on main's merge commits, which are
not ancestors of dev. To backfill from a shipped version, find its
dev-side anchor locally with git merge-base origin/dev vX.Y.Z and pass the
resulting commit SHA as base_ref.
Release notes in Linear
The RC PR body is the hand-written, customer-facing release notes (see the
referenta-release-notes skill) and the single source of truth. Both jobs
mirror that body into the Linear release's notes (the action's release_notes
input, with the Codesmith footer stripped): the sync job mirrors it while
the release is at Staging — so QA sees the current description as it is
written and edited on the open RC PR — and the promote job mirrors the final
body on completion. The result is that the Linear changelog, the GitHub Release,
and the dashboard all show the same text. Mirroring only runs when the body has
content, so an empty body never clears notes already mirrored.
Because the notes are supplied explicitly, auto-generate-on-completion should be turned off in the pipeline settings (Settings → Releases → Referenta App) so Linear does not overwrite the mirrored notes with agent-generated ones.
If you ever want Linear to draft notes instead — e.g. a release with no hand-written body — generate them manually from the Changelog tab. The house-style template below is kept version-controlled for that case; paste it into the pipeline's Template field:
You are writing user-facing release notes for Referenta, a SaaS platform for political and public-sector work. The audience is end users, not developers — describe what changed for them, never how it was built. Analyze the issues included in this release and produce notes in the exact format below.
LANGUAGE: Bilingual. Write German first, then a line with only `---`, then English. The English section mirrors the German one section-for-section. Use the formal German register ("Sie").
STRUCTURE (apply within each language):
1. Lead with the release's main feature as a heading: `### <Headline feature> — <short tagline>`, followed by one framing sentence using this release's version number:
- DE: "Das Highlight von <version>: <one sentence>."
- EN: "The headline of <version>: <one sentence>."
Then bold-lead sub-bullets: "- **<capability>** — <what it does for the user>."
2. Add further `### <themed section>` headings for other notable features, grouped by product area (e.g. Assistent, Recherche, Knowledgebase, Press Monitor, Organisationsverwaltung). Use bullets, with bold lead-ins where they help.
3. Close with `### Feinschliff & Fehlerbehebungen` (DE) / `### Polish & fixes` (EN): list only user-visible fixes, and put the internal catch-all line as the LAST bullet.
RULES:
- Classify each issue. User-facing → describe concretely. Internal (refactors, security, tooling/CI, dependency bumps, tests, infra, performance with no visible effect) → do NOT itemize; fold all of it into a single final bullet, verbatim:
- DE: "Interne Architektur- und Developer-Experience-Verbesserungen."
- EN: "Internal architecture and developer-experience improvements."
- Never include ticket IDs (REF-N), PR or commit references, branch names, or internal jargon.
- Do not restate the version in section headings (only in the framing line).
- Keep it concise, professional, and benefit-first.QA Handover And Sign-Off
Once the Staging sync has run, QA works the Release Candidate entirely in Linear. The release object lists every issue in the RC, and each issue carries a QA status. The board flow is:
In Review → On Staging → QA Approved → Merged on Prod
Each transition is driven by a different mechanism:
On Stagingis set automatically when a feature PR merges intodev. A Linear Git automation (a branch rule where merging todevsets the status toOn Staging) marks the issue as landed on staging and ready to test. Merging todevalso advances the RC PR, so the Staging sync re-runs.QA Approvedis set manually by QA after testing the issue on the staging deploy. This is the only human step in the flow. If an issue fails, QA moves it back toIn Progress; the developer fixes it, re-merges todev, and the next sync returns it toOn Stagingfor re-test.Merged on Prodis set automatically when the release is completed (on merge tomain). A Linear release automation moves every issue in the completed release to its final status.
QA status lives natively in Linear, which every developer can already see, so it is deliberately not mirrored onto the GitHub PR: GitHub carries the code and the release, Linear carries the issues and their QA state.
How The Dashboard Uses Release Data
The Referenta dashboard reads release information directly from GitHub:
getGithubReleasePayloadfetches release and tag data from the repository,/release-notesrenders that data throughReleaseNotesView,- the page body is taken from the GitHub Release description, and
- the sidebar version badge uses
/api/v1/github/latest-tagtogether withuseGithubTag.
In product terms, GitHub Releases are the content source for the dashboard's release communication. Updating a GitHub Release updates what the dashboard can display.
Freshness Model
The dashboard behavior is automatic, but it is not true push-based realtime.
The current implementation uses cached fetches and revalidation:
- the release API route is revalidated on a timed cache window,
- the latest-tag API response is cached server-side, and
- the client hook stores the latest tag in session storage for the current browser session.
So release updates propagate automatically on subsequent fetches and refreshes, but not through a live websocket-style broadcast channel.
Practical Release Flow
The expected release workflow is:
- validate changes on
dev, - open a PR from
devtomain, - title it
Release Candidate: vX.Y.Z(orRC: vX.Y.Z/Release: vX.Y.Z), - write the release notes in the PR body,
- merge the PR, and
- let GitHub Actions create the tag and GitHub Release that the dashboard consumes.
That gives Referenta one release source of truth and keeps versioning, release notes, and dashboard display aligned.