Concourse Release Workflow
This is the release workflow that replaces Doof. An app on it is released from
Slack with /doof, verified through a GitHub issue checklist, and promoted to
production by closing that issue. There is no release-candidate branch and no
release branch: the same image that ran in QA is the image that runs in
production, and the release is identified by a calendar version tag.
Apps still on the legacy workflow are described in Webapp release process. Doof drives those until they are migrated.
Which workflow is my app on?
One field in src/bridge/settings/apps.py in ol-infrastructure
(AppRegistration.release_resource_workflow) decides this, and both the
Concourse pipeline generator and the release bot read it. That file is the
authoritative answer for any app.
If you run a /doof release command against an app that has not been migrated,
the bot refuses and says so. That refusal is deliberate: the jobs and artifacts
every command reaches for only exist in the new pipeline shape, so without it
the mutating commands would 404 in Concourse and the read-only ones would answer
confidently with the wrong version and the entire commit history.
The shape of a release
sequenceDiagram
actor eng as Engineers
participant main as main branch
participant cc as Concourse
participant gh as GitHub
participant bot as Release bot
participant qa as QA/RC
participant prod as Production
loop day to day
eng->>main: Merge pull requests
main->>cc: CI image build
cc->>qa: Deploy to CI
end
Note over eng,bot: /doof release APP
bot->>cc: Trigger the release build
cc->>cc: Compute next version YYYY.M.D.N
cc->>gh: Cut the release branch, tag pre-bump HEAD
cc->>cc: Build image tagged with the version
cc->>qa: Deploy that image to QA
cc->>gh: Open release issue with per-author checklist
cc->>gh: Record RC Deployment
eng->>qa: Verify your changes
eng->>gh: Check your boxes
bot->>gh: Poll the checklist
bot-->>eng: Post "ready to promote" in Slack
Note over eng,bot: Promote (button, /doof promote, or close the issue)
gh->>cc: Closed release issue triggers production
cc->>prod: Deploy the same image
cc->>gh: Record Production Deployment
cc->>main: Merge the release branch back, delete it
Closing the release issue is the promotion gate. The Slack button and
/doof promote are two ways of closing it; closing it by hand in GitHub works
just as well.
Versions
Versions are calendar versions of the form YYYY.M.D.N, for example
2026.9.3.1: the first release on a day is .1 and the counter resets the
next day. The month and day are not zero padded, because uv and PEP 440
reject leading zeros.
The version tag is planted on the commit the release was cut from, before the
version bump. action: finish then lands two more commits on the default
branch, Release <version> and Merge releases/<version>. Both are excluded
from commit counts, checklists and changelog entries, so a finished release does
not propose its own bookkeeping as the next release's contents.
Commands
All of these are subcommands of one slash command, and replies post visibly to the channel rather than only to you.
| Command | What it does |
|---|---|
/doof release <app> |
Cuts a release: next version, release branch and tag, image build, QA deploy, release issue. |
/doof hotfix <app> <sha> |
Cuts what production runs plus one commit. See below. |
/doof preview <app> |
Shows what the next release would contain. Changes nothing. |
/doof release-notes <app> |
Lists the unreleased commits. |
/doof release-status [app] |
Reports release issue status, for one app or all of them. |
/doof wait-for-checkboxes <app> |
Watches a checklist and reports each author as they finish. |
/doof promote <app> |
Closes the release issue, which triggers the production deploy. |
/doof abandon <app> |
Tears down an in-flight release (branch and tag) and cancels a pending hotfix request. |
/doof publish <app> |
Not usable yet. No generated pipeline defines a publish job and no library is registered. |
Verifying a release candidate
The release issue body is a task list grouped by author, generated from the
commits in the release. Verify your own changes in QA and check your boxes. When
every box is checked the bot posts a "ready to promote" message with a button
into the app's Slack channel, mentioning whoever ran /doof release.
Two things about that message are worth knowing. The checklist itself mentions
nobody: the authors in it are commit email addresses written into a GitHub issue.
/doof wait-for-checkboxes <app> is where authors get @-mentioned, by resolving
those emails through Slack's user lookup. And the promote-ready label the bot
applies is bookkeeping to stop the notification repeating, not a gate signal.
Hotfixes
A hotfix is production plus one commit, not the default branch plus one commit. The default branch usually carries unreleased work, so cherry-picking onto its HEAD would either ship everything unreleased or fail on an empty cherry-pick.
/doof hotfix <app> <sha> pushes a hotfix/<full sha> tag to the repo, which is
how the SHA reaches a Concourse job (triggering a job carries no parameters).
The next release check sees that tag and hands the build a hotfix instead of a
normal release. The tag is deleted as soon as the cut starts, whether or not it
succeeds, so a failed request cannot turn a later release into a hotfix.
It refuses, rather than guessing, when:
- another release is in flight (promote or abandon it first),
- GitHub cannot confirm that the last release actually deployed to production,
- the commit is already contained in production.
While a hotfix request is pending, /doof release refuses, because a release cut
at that moment would cut the hotfix instead. /doof preview reports the pending
hotfix rather than a preview, so the two do not contradict each other.
In-flight releases, superseding, and abandoning
A release is "in flight" when its releases/<version> branch still exists on
the remote, which means it was cut but never finished. That is a statement about
git, not about deployment: a release whose production deploy succeeded but whose
merge-back failed is still in flight by this definition.
/doof release on an app with an in-flight release supersedes it: the old
release branch is deleted, and its tag with it if that release never reached
production. A release that did reach production keeps its tag, because the tag is
the only thing tying what production runs back to a commit. Doof refused a second
release instead. The change is deliberate: pinning to the in-flight version froze
the resource indefinitely after a single failed merge-back.
/doof abandon <app> deletes the in-flight release's branch and tag and cancels
any pending hotfix request. Use it when a release was cut and must not ship.
What is actually running in production
A closed release issue means promotion was authorized, not that production is
running that version. What production is running is recorded as a GitHub
Deployment on the Production environment of the app's repo, and the bot posts
a message when an RC or Production deployment reaches success.
Differences from Doof worth knowing
- Invocation changed. Doof was
@doof <command>in a channel bound to a project. This bot is/doof <subcommand> <app>from anywhere, with the app named. previewandabandonare new.hotfixwas rebuilt on the new machinery.- Doof's
version,hash,what needs review,start new releasesanduptimehave no equivalent yet. - Doof narrated the whole lifecycle in Slack. This bot posts a ready-to-promote
message, RC and production deployment milestones, and a nag when a release has
sat cut-but-unfinished for 24 hours. Rollout narration comes from kubewatch,
routed by the
ol.mit.edu/slack-channelpod label rather than by the release. - The release PR state labels Doof maintained (
WAITING_FOR_CHECKBOXESand the rest) are gone. The release issue carries onlyreleaseandpromote-ready.
When something looks stuck
- The release job binds a version when the build is scheduled, so a build
triggered without a fresh resource check reuses the previous version. The bot
always forces that check and refuses to trigger if it fails; if you trigger
build-<app>-release-imageby hand in Concourse, check the<app>-releaseresource first. - A release cut but not finished for more than 24 hours gets a Slack nag. Read it as a real signal: it usually means the merge-back failed.
- The production deploy waits on the
<app>-release-gateresource noticing the closed issue./doof promoteand the Slack button force that check; closing the issue by hand leaves the deploy waiting for the resource's normal poll.
For how the machinery underneath works, see Release workflow machinery.