Promote a stable branch to each release for the plugin to install from
What
- A new
promote-stablejob in.github/workflows/release.ymlfast-forwards astablebranch to the release tag.- It needs
release, so it runs only once GoReleaser has published. - Like
sync-skills, it skips prerelease tags and any tag that isn't the latest stable release. - It moves the branch through the git refs API with
force=false, so GitHub refuses anything but a fast-forward.
- It needs
RELEASING.mdcovers the new branch: in the release steps, the stable/prerelease table, a short "Plugin install source" section (including what to do if the job fails), and the distribution channels table.internal/release/manifests_test.gogets a wiring test. It requires the job to needrelease, skip prereleases, havecontents: write, skip non-latest releases and passforce=false.
Why
The 37signals marketplace installs this repo's Claude Code and Codex plugin from main. People install the basecamp command from releases, so between releases a fresh plugin install can get skills that call commands no released CLI has. 4, 5 and 8 commits touched skills/ in the last three release cycles.
basecamp/plugins#13 points the marketplace at stable, and this keeps stable on the latest release.
I moved the branch from the release workflow rather than scripts/release.sh for two reasons. Pushing the tag only starts the build, so moving stable there would publish skills before the binaries exist, and would still move it if the build failed. And this way no other repository has to change per release. It replaces basecamp/plugins#12, which moved commit pins with an hourly workflow and needed a merged PR in that repo for each release.
Card: https://app.basecamp.com/2914079/buckets/48521764/card_tables/cards/10423396016
Testing
-
make checkpasses:bin/ciexited 0 locally. - Workflow lint:
actionlintandzizmorreport nothing onrelease.yml. zizmor had 3 ignored and 25 suppressed, all already present. - The test catches what it's for: it fails if the job's
if:loses the prerelease exclusion, and fails ifforce=falsebecomesforce=true. stablealready exists atv0.14.0(2dbca87a), so #13 can merge first. Therefs/heads/stablerule set is only Copilot code review, which doesn't block ref updates.- The API behaviour the job relies on, checked against the real
stable:- Updating it to the commit it already points at succeeds, which is what a re-run does.
- Updating it to v0.13.2 with
force=falsewas refused withUpdate is not a fast forward(HTTP 422), andstablestayed at v0.14.0.
- Not yet exercised: the job itself. It runs on the next stable release, and it should log
stable is now vX.Y.Z (<sha>). If it fails, the release is still out, and rerunning the job recovers it.
Summary by cubic
Adds a promote-stable release job that fast-forwards the stable branch to each new stable release, so the 37signals marketplace installs plugin skills from the latest release instead of whatever is on main.
Details
- The job runs only after the release has published, and only for the latest stable tag; prereleases and older tags leave
stablealone. - It uses
force=false, so GitHub rejects any update that isn't a fast-forward, keepingstablefrom moving backwards. - If the job fails, the release still goes out but plugin users stay on the previous stable; rerunning the job recovers it.
- Updates
RELEASING.mdwith the new branch behavior and adds a wiring test covering the job's guards.