Make release workflow trigger on version bump merge, not dispatch - #126
Conversation
Releasing took two deliberate acts from the maintainer that were really the same decision: merge the version-bump PR, then go dispatch `release` with the same version the PR just landed. The second one carried no new judgement — it restated the first — but it was the one step that couldn't be done from a review page, so a release stalled on someone opening the Actions tab. `release.yml` now also triggers on a push to `main` that touches `App/Blurt/project.yml`, and a new Linux `resolve` job decides whether that push is actually a release: it diffs `CFBundleShortVersionString` against the push's previous commit and checks no `vX.Y.Z` tag exists yet. A project.yml edit that adds a source file resolves to `release=false` and skips the build and publish jobs; only a bump landing on main goes through. Running first, on ubuntu, means a non-release push costs seconds and never reaches the runner holding the Developer ID key. The dispatch path stays, and is what re-runs a failed build, republishes, and does the non-main dry run — `skip_checks`, `skip_smoke`, and `republish` are dispatch-only inputs, so the merged-bump path always runs the full checks. Both jobs still pin `github.sha`, so a release is still exactly one reviewed commit, and publishing still parks on the `release-publish` approval gate. The `resolve` job supersedes the build job's version guard as the source of the version; the guard stays as a re-check on the signing runner itself, so the artifact name and the built binary can't disagree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F952B3qz1QeXYv5sPGwPh3
Dev buildDownload Blurt.app — built from Installing itcd ~/Downloads
unzip -o blurt-dev-build-pr-126.zip # GitHub wraps every artifact in a zip
unzip -o Blurt-dev-93f869d.zip
find Blurt.app -exec xattr -c {} + # clear quarantine: xattr lost -r in macOS 12.3
rm -rf /Applications/Blurt.app && cp -R Blurt.app /Applications/
open -a BlurtIt is ad-hoc signed and not notarized: Gatekeeper refuses to open it until Expect that re-grant once per dev build, including a second build of this tccutil reset Accessibility dev.alex.blurt |
The last dispatch in the release flow was the first one. Dispatching a workflow needs the Actions UI or `actions: write`; a chat or web session typically has neither, but does have push. So the one step that could kick a release off was the one step an agent couldn't do, and every release still began with the maintainer opening the Actions tab. `release-bump.yml` now also triggers on a push of `release/v[0-9]*`. The branch name is the request — `release/v0.1.37` asks for 0.1.37, with no default_target guessing, because a marker that named no version would be asking the workflow to guess what its own name meant. The job checks out main, verifies the marker is an ancestor of main, bumps on top of main's tip, and force-pushes the result onto the same branch with the lease pinned to the sha it vetted. The ancestor check is the load-bearing one. Without it, pushing a branch would be a way to get a bot-authored commit sitting on top of arbitrary content, and the PR that followed would quietly be about more than a version bump. With it, the marker is a signal and the bump is the only commit that lands on it. This leans on the GITHUB_TOKEN rule in the opposite direction from the rest of the release path: the bump commit the job force-pushes does not re-fire the push trigger that started it, which is what keeps it from looping. Noted in the workflow, RELEASE.md, and the skill's guardrails, since moving this job to a PAT would silently turn it into a loop. Dispatch still works, still guards that it came from main, and remains the only path that accepts an empty version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F952B3qz1QeXYv5sPGwPh3
What & why
The release workflow now triggers automatically when the version-bump PR merges into
main, rather than requiring a manual dispatch. This simplifies the release process from three steps (bump, merge, dispatch) to two (bump, merge).How it works:
pushtrigger torelease.ymlthat fires whenApp/Blurt/project.ymlchanges onmainresolvejob (Linux, cheap) runs first to decide whether to release:vX.Y.Ztag exists yetbuildandpublishjobs now depend onresolveand only run if it setsrelease=trueskip_checks,skip_smoke, orrepublish— those remain dispatch-only escape hatchesSecurity/correctness preserved:
github.sha, so the tag lands only on the exact reviewed commitrelease-publishapproval gate still holds before anything reaches usersrepublish, and non-maindry runsGITHUB_TOKENmerge (Actions auto-merge) won't triggerrelease— the merge must come from a person or an agent with its own credentials, same as the PR creation ruleUpdated
RELEASE.md,.claude/skills/release/SKILL.md, andAGENTS.mdto document the new flow and the two ways a release can start.How it was tested
scripts/check.shpasses (or CI will, if I'm not on a Mac)https://claude.ai/code/session_01F952B3qz1QeXYv5sPGwPh3