Repository navigation
feat(release)!: run the release pipeline as mega-gutenberg - #55
Merged
Merged
Conversation
…ing the settle PR
…rg; drop direct mode
release-candidate cuts on push to the default branch through release-route; release-settle refreshes the long-lived settle PR on push to release-v* and publishes when it merges. Every token is a mega-gutenberg token minted in the release environment. release-publish.yml is gone, on-release.yml comments name release-publish (gutenberg) as the Release creator, and app-token-lint.yml adds the per-repo lint. copy_templates.py renders the release templates for one repository into a fresh directory and fails on an unfilled placeholder or a with: key that is not an input of the called action; its unit test runs in actions-test.yml.
A push to a prefix branch whose suffix is not X.Y.Z (e.g. release-vnext) made the action's normalize call and release_tools route abort. The push now routes to skip with a reason naming the branch, a manual dispatch leaves such branches out of the targets, JSON null fields act like absent ones, wrong types fail with one release_tools line instead of a traceback, and a stale settle PR without a marker no longer prints None. When route rejects its input the step fails with a Release route error annotation.
Troublor
marked this pull request as ready for review
October 27, 2026 15:44
GitHub ignores workflow env: for its default GITHUB_* variables, so the release-route job's GITHUB_REPOSITORY: megaeth-labs/fake never reached the scripts on a runner; the extracted publish guard asked for repos/megaeth-labs/.github/pulls/42 and the fake gh exited 99. Pass the fake repository explicitly to route.sh and publish-guard.sh, pin every fake gh call to it (the commits/pulls and repo view patterns used to accept any repository), and check that repo-less gh calls would resolve to the fixture's fake remote.
cut: a release branch already at the candidate merge commit is this cut, done; exit 0 with a notice. At any other commit it still refuses, now naming both SHAs. A ref create that loses a race re-reads the ref and applies the same rule. propose: the base_commit branch create and the stale candidate-branch delete tolerate a retry or a concurrent run that got there first. publish: a tag at the merge commit whose Release already exists is a retry of a finished run; exit 0 with a notice and skip both writes. A tag push or Release create that loses a race keeps the winner's when it is at this commit. A tag elsewhere still refuses; the settle-PR re-verification is unchanged. The release-candidate and release-publish harness jobs assert each rule against a bare remote and a fake gh that fails like GitHub's 422.
…efresh idempotent
The settle PR body now opens with an [!IMPORTANT] alert saying that merging
it settles vX.Y.Z (the merge commit is tagged, released and built), then the
version line, then the unchanged `<!-- release-settle: vX.Y.Z <sha> -->`
marker, then the notes. The title reads
`chore(release): settle vX.Y.Z — merging publishes the release` and is
re-applied on refresh.
Ensuring the PR is now safe to re-run and to race:
- a lost `gh pr create` ("already exists") or a rejected push re-reads the
open PR and refreshes it instead of failing;
- deleting a stale settle branch tolerates 404/422, and a retry pass never
deletes a branch a concurrent run just pushed;
- the up-to-date exit also requires the current title;
- the branch is pushed only once the merge and the changelog commit both
succeed, each pass starts from the remote (checkout --force), and a
failure names the stage reached.
test_release_settle.py drives the shipped step against a real local remote
with a fake gh. The release-settle harness also asserts that a refresh keeps
the same PR.
… tags, and lint permission-less tokens - release-route and release-publish share one order rule (release_tools order_refusal / check-order): a settle PR is published only when its version is newer than every other vX.Y.Z tag. Publish checks it in the guard and again right before the tag push, and marks the Release latest only when it is newer than the current latest Release. - The latest tag is picked by release_tools latest-tag everywhere: only vX.Y.Z tags count, others (v1.2.0-rc1) are named and ignored, so they can no longer fail routing. - app-token-lint L6: a Maxwell or Gutenberg token, and any app token in a release job, must request explicit permission-* inputs. - release-settle re-checks the tag and the branch tip before every remote write, so a late refresh never opens a settle PR for a published version. - release-route fails, naming the file and pattern, when it cannot read the version file, instead of skipping as "not landed". - copy_templates quotes substituted values that a plain YAML scalar would change (bump_command with " #"), and checks they read back exactly.
…ndidate instead peter-evans/create-pull-request updated an open candidate PR's branch in place, which force-pushed chore/release-candidate-*: the last force push in the release pipeline. The candidate PR is now opened by an explicit step that never rewrites a ref. A re-dispatch closes the open candidate PR with a comment, deletes its branch, pushes the new commit as a new ref with a plain git push and opens a fresh PR with gh, with the same title, body (including the release-candidate marker), base, labels and app authorship. Every state a retry or a concurrent run can leave (a PR already closed, a ref already gone, a branch pushed with no PR, a branch or PR another run made first) converges on one branch and one open PR. The harness runs the step against a remote that denies non-fast-forward updates. test_no_force_push.py fails if any shipped action or template force-pushes (--force, --force-with-lease, -f, +refspec, --mirror, create-pull-request, force=true ref updates); release-publish's git fetch --force of a tag refspec is allowed explicitly. The docs now say nothing force-pushes any branch, with no ruleset exemption needed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Runs the shared release pipeline as the mega-gutenberg GitHub App instead of mega-maxwell. Maxwell is read-only and is excluded from the release pipeline entirely (plan decision D12), including the private-dependency tokens that release jobs read.
Release settling is push-driven and the old settle/explicit-publish split is gone. Merging the settle PR is the release decision, and the PR says so in an
[!IMPORTANT]alert at the top.Breaking changes for consumers
settle_modeandsettlersinputs are removed.cutand publish now requirecommitandpr_numberoutputs fromrelease-route; callers that do not pass them will fail.What is in this PR
release-tools: the routing decision, plus hardening so no branch name, tag or null field can crash it.release-routeandrelease-settlecomposite actions, the latter rewritten as a long-lived bot-maintained settle PR.release-candidateandrelease-publishon the gutenberg identity, publish requiring the merged settle PR.app-token-lintand its action.workflow-templates/with the copy helper.docs/RELEASING.md, the operator runbook.Design rules this PR now enforces
chore(release): settle vX.Y.Z — merging publishes the release.create-pull-request's--force-with-lease: it closes the open candidate PR, deletes the head ref, plain-pushes a fresh branch and opens a new PR.test_no_force_push.pyfails the build if any action ever gains a force push.Defects found by review, fixed here rather than later
vX.Y.Ztag, and the Release is marked latest only when it truly is.X.Y.Zv*tag (v1.2.0-rc1) was chosen as "latest" and made routing fail permanently. One sharedlatest-taghelper now ignores those tags everywhere.app_token_lintaccepted an app token with nopermission-*inputs, which inherits contents write. It now rejects them.copy_templates.pyrendered unquoted values, so a#in a substituted value truncated it.release-routeharness was not hermetic on a real runner: GitHub ignoresenv:overrides for its own defaultGITHUB_*variables, so the job silently used the real repository. The harness now passes the repository explicitly on every product invocation.Test plan
release-route,release-settle,release-candidate,release-publishand the templates lint.action_docs.py --check0, actionlint at the one pre-existing SC2001,app_token_lintclean over the whole tree.Troublor <14846504+Troublor@users.noreply.github.com>with no co-author trailer.