Skip to content

Commit 4ffee49

Browse files
committed
fix: recover exact Release Please runs
1 parent a33c128 commit 4ffee49

8 files changed

Lines changed: 2330 additions & 321 deletions

File tree

.github/workflows/publish.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,18 @@ jobs:
157157
RELEASE_JSON="$release_json" TAG_COMMIT="$tag_commit" \
158158
node --input-type=module <<'EOF'
159159
import { readFileSync } from "node:fs";
160-
import { validateGitHubRelease } from "./scripts/release-workflow-validation.mjs";
160+
import {
161+
extractReleaseNotesFromChangelog,
162+
validateGitHubRelease,
163+
} from "./scripts/release-workflow-validation.mjs";
161164
162165
const release = JSON.parse(readFileSync(process.env.RELEASE_JSON, "utf8"));
166+
const version = JSON.parse(readFileSync("package.json", "utf8")).version;
163167
validateGitHubRelease(release, {
168+
expectedBody: extractReleaseNotesFromChangelog(
169+
readFileSync("CHANGELOG.md", "utf8"),
170+
version,
171+
),
164172
htmlUrl: process.env.RELEASE_HTML_URL,
165173
releaseCommit: process.env.RELEASE_COMMIT,
166174
tag: process.env.RELEASE_TAG,

.github/workflows/release-please.yml

Lines changed: 768 additions & 162 deletions
Large diffs are not rendered by default.

ARCHITECTURE.md

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -116,39 +116,53 @@ For normal stable patches, Release Please owns the reviewed version/changelog
116116
PR and the immutable tag and GitHub Release. The configuration uses an explicit
117117
`cometapi` component and `always-bump-patch` versioning so the authorized 0.1
118118
maintenance window cannot enter 0.2 implicitly and a root package does not fall
119-
into the single-package tag-discovery ambiguity encountered during 0.1.0.
119+
into the single-package tag-discovery ambiguity encountered during 0.1.0. The
120+
workflow also rejects commit-level `Release-As:` notes before mutation because
121+
Release Please applies those overrides before its patch versioning strategy.
120122
Because a
121123
GitHub Release created with the default `GITHUB_TOKEN` does not start a separate
122124
`release.published` workflow, publication is chained from the successful
123125
Release Please workflow. The handoff accepts only the canonical repository's
124-
successful first-attempt `push` run for `main` at the still-current exact `main`
125-
SHA. The release workflow records `release_created`, SHA, tag, version, URL,
126-
repository, workflow identity, run ID, and attempt in an exact-run artifact.
127-
Publication downloads and validates that artifact before checking the tag and
126+
successful attempt-qualified `push` run for `main` at the still-current exact
127+
`main` SHA. The release workflow records normalized action outcome, recovery
128+
state, pre-action Release presence, the exact Release-producing attempt, SHA,
129+
tag, version, URL, repository, workflow identity, run ID, and attempt in a
130+
schema-v2 exact-run artifact. Publication
131+
downloads and validates only that attempt's artifact before checking the tag and
128132
immutable Release. A first-attempt manual run is explicitly release-inert and
129-
must succeed only after validating one action-created patch PR; its event cannot
130-
enter publication. Any successful `push` run without the exact Release
131-
Please-created result, tag, and immutable Release fails before live or registry
132-
access. The release outcome and package artifact are verified independently.
133+
must succeed only after independently validating one canonical action-created
134+
patch PR; its event cannot enter publication. Any successful `push` run without
135+
the exact result artifact, tag, and immutable Release fails before live or
136+
registry access. The release outcome and package artifact are verified
137+
independently.
133138

134139
Release Please and publication remain separate trust domains. Release Please
135140
does not receive npm OIDC permission; `id-token: write` remains limited to the
136141
protected publish job. Repository variables gate both flows, and reruns remain
137142
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
138-
Release Please itself rejects attempt 2 or later before repository mutation;
139-
the explicitly enabled preparation path uses a new manual dispatch with Release
140-
creation disabled, while only a new `push` run can create the Release and enter
141-
publication. The authorized Actions setting lets the default token create the
142-
PR, but bot review cannot satisfy the gate. A merged release PR is accepted for
143-
tagging only after a distinct repository administrator approved its final head.
144-
The workflow checks the triggering SHA against the fetched `main` tip both at
145-
checkout and immediately before Release Please mutation, so an older queued run
146-
cannot release a newer default-branch commit.
147-
It also rejects any pending merged release PR whose merge commit is not the
148-
current push SHA, and scans the complete pending merged set so a legacy, fork,
149-
alternate, older, or additional PR cannot be tagged. Manual dispatch is
150-
therefore release-inert: it may prepare one canonical action-created PR only
151-
when no merged release PR is awaiting a tag.
143+
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
144+
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
145+
candidate, and final-head review. It may retry while the tag and Release remain
146+
absent. If an earlier attempt already created the Release, recovery accepts only
147+
the exact bot-authored immutable Release at that SHA whose publication time
148+
falls inside exactly one earlier Release Please step from the same run.
149+
Release-mode action failure is tolerated only long enough to
150+
prove that postcondition, reconcile the release PR to `autorelease: tagged`, and
151+
write the attempt-qualified artifact. The authorized Actions setting lets the
152+
default token create the PR; the resulting approval-required CI still needs a
153+
human with write access to authorize execution, and bot review cannot satisfy
154+
the release gate. A merged
155+
release PR is accepted for tagging only after a distinct repository
156+
administrator approved its final head. The workflow checks the triggering SHA,
157+
release-branch snapshot, all open and closed PR identities, current review, and
158+
tag/Release state immediately before Release Please mutation, then rechecks
159+
`main`, the release branch, the complete PR snapshot, exact final-head approval,
160+
and the Release body against `CHANGELOG` before accepting the result. It rejects any
161+
pending merged release PR whose merge commit is not the current push SHA and
162+
scans the complete pending merged set so a legacy, fork, alternate, older, or
163+
additional PR cannot be tagged. Manual dispatch is therefore release-inert: it
164+
may prepare one canonical action-created PR only when no merged release PR is
165+
awaiting a tag.
152166

153167
## Testing layers
154168

RELEASING.md

Lines changed: 73 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -234,22 +234,26 @@ The repository maintains four independently auditable workflows:
234234
`LIVE_SMOKE_ENABLED=true`.
235235
- `release-please.yml`: patch-only versioning, an explicit `cometapi` component,
236236
separate pull requests, and an explicit component/version title. It requires
237-
`RELEASE_PLEASE_ENABLED=true`, accepts only first-attempt runs, and uses the
238-
default `GITHUB_TOKEN`. The authorized repository baseline keeps default
239-
workflow permissions read-only and allows Actions to create pull requests; it
240-
does not make bot review valid release approval. A first-attempt manual
241-
dispatch runs with GitHub
242-
Release creation disabled and prepares exactly one action-authored patch PR
243-
after the variable is enabled; rerunning an older workflow is rejected before
244-
Release Please can mutate repository state. The preparation run succeeds only
245-
after verifying the canonical branch, title, machine-readable body, pending
246-
label, four expected release files, and 0.1.x patch versions. Before a
247-
post-merge `push` may create a Release, the workflow scans every merged `main`
248-
PR carrying `autorelease: pending`, rejects legacy, alternate, fork, older, or
249-
multiple candidates, and requires an administrator's human approval on the
250-
exact final head. Release Please then creates the normal tag and GitHub
251-
Release, transitions the release label, and uploads its exact release outputs
252-
as a run-bound artifact.
237+
`RELEASE_PLEASE_ENABLED=true` and uses the default `GITHUB_TOKEN`. The
238+
authorized repository baseline keeps default workflow permissions read-only
239+
and allows Actions to create pull requests; it does not make bot review valid
240+
release approval. A manual dispatch is attempt-1-only, runs with GitHub Release
241+
creation disabled, and prepares exactly one action-authored patch PR after the
242+
variable is enabled. A new dispatch may revalidate an unchanged canonical PR
243+
even when Release Please returns no PR output. The preparation run succeeds
244+
only after independently verifying the canonical branch, title,
245+
machine-readable body, pending label, four expected release files, and 0.1.x
246+
patch versions. Before a post-merge `push` may create a Release, the workflow
247+
scans every merged `main` PR carrying `autorelease: pending`, rejects legacy,
248+
alternate, fork, older, or multiple candidates, and requires an
249+
administrator's human approval on the exact final head. A same-run push retry
250+
may proceed only for the same run ID, SHA, candidate, and review. An existing
251+
tag and Release are accepted only as the exact bot-authored immutable Release
252+
whose publication time falls within exactly one executed Release Please step
253+
from an earlier attempt of that run. Release Please then creates or recovers
254+
the normal tag and GitHub Release, verifies its notes byte-for-byte against the
255+
normalized `CHANGELOG` entry, reconciles the release label, and uploads a
256+
schema-v2 attempt-qualified result artifact.
253257
The triggering SHA must still equal the fetched `main` tip at checkout and
254258
immediately before the Release Please action; an older queued run stops before
255259
mutation.
@@ -262,11 +266,13 @@ The repository maintains four independently auditable workflows:
262266
Please workflow for `main`. This indirection is required because a GitHub
263267
Release created with the default `GITHUB_TOKEN` does not trigger a new
264268
`release.published` workflow. The handoff accepts only the canonical
265-
repository's successful, first-attempt `push` run for the still-current exact
266-
`main` SHA. It downloads the output artifact from that exact upstream run and
267-
requires `release_created`, SHA, tag, version, URL, repository, workflow path,
268-
run ID, and attempt to agree before accepting the matching Release
269-
Please-created version tag and immutable GitHub Release. A successful manual
269+
repository's successful attempt-qualified `push` run for the still-current
270+
exact `main` SHA. It downloads the output artifact from that exact upstream
271+
run ID and attempt and requires schema version, normalized action outcome,
272+
recovery state, pre-action Release presence, exact Release-producing attempt,
273+
SHA, tag, version, URL, repository, workflow path, run ID, and attempt to agree
274+
before accepting the
275+
matching version tag and immutable GitHub Release. A successful manual
270276
preparation run is release-inert and cannot enter publication; a successful
271277
`push` run without the exact release result fails before live or publication
272278
work. The release path then packs and tests one exact, attempt-qualified
@@ -470,24 +476,46 @@ The release workflow must never change either setting, and any later drift is a
470476
stop condition.
471477

472478
After enabling `RELEASE_PLEASE_ENABLED`, start a new manual dispatch on `main`;
473-
do not rerun the skipped workflow from the repair merge. Only attempt 1 may call
474-
Release Please. The workflow rejects any dispatch whose triggering ref is not
475-
`refs/heads/main`, and all preparation and release runs share one main-scoped
476-
concurrency group. The manually dispatched preparation run cannot trigger npm
477-
publication or create a Release: the action receives explicit
479+
do not rerun the skipped workflow from the repair merge. Only attempt 1 of a
480+
manual dispatch may call Release Please; an unchanged PR is restarted with a
481+
new dispatch, not a rerun. The workflow rejects any dispatch whose triggering
482+
ref is not `refs/heads/main`, and all preparation and release runs share one
483+
main-scoped concurrency group. The manually dispatched preparation run cannot
484+
trigger npm publication or create a Release: the action receives explicit
478485
`skip-github-release=true`, it is accepted only when no merged
479486
`autorelease: pending` PR exists, and `publish.yml` accepts only an upstream
480-
`push` event. It must succeed after validating the one action-created 0.1.1 PR.
481-
Before mutation, the workflow also rejects any open PR whose head name could be
482-
mistaken for the canonical release branch, including a same-named fork branch.
487+
`push` event. It must succeed after independently validating the one canonical
488+
action-created 0.1.1 PR, including the unchanged-PR case where the action emits
489+
no `prs` output. Before mutation, the workflow also rejects any open PR whose
490+
head name could be mistaken for the canonical release branch, including a
491+
same-named fork branch.
492+
The `GITHUB_TOKEN`-created PR's `pull_request` CI starts in GitHub's
493+
approval-required state. A human with write access must explicitly authorize
494+
those workflow runs before their results can satisfy required checks; this is
495+
separate from the final-head administrator review.
483496
Remove the one-cycle `last-release-sha` from that branch, complete the
484497
release-ready documentation, run the full matrix on its final head, and obtain
485498
approval from a different human repository administrator. The release-PR merge
486-
creates the new first-attempt `push` run that may tag and publish. A later push
487-
cannot tag an older outstanding release PR; its merge SHA must equal the
488-
triggering SHA before Release Please runs. Immediately before the irreversible
489-
Release Please call, the push run also requires final release metadata and
490-
public documentation, including removal of the one-cycle `last-release-sha`.
499+
creates the `push` run that may tag and publish. A later push cannot tag an older
500+
outstanding release PR; its merge SHA must equal the triggering SHA before
501+
Release Please runs. A rerun may retry that same candidate while no tag or
502+
Release exists. If Release Please created the immutable Release but failed
503+
before producing outputs, only the same run may recover it, and only after
504+
proving its exact SHA, tag, bot author, immutable state, target, URL, notes, and
505+
publication inside exactly one earlier Release Please step time window. Recovery
506+
then idempotently removes `autorelease: pending`,
507+
adds `autorelease: tagged`, and writes a schema-v2 artifact for that attempt.
508+
Immediately before the irreversible Release Please call, every attempt also
509+
reconfirms `main`, the release-branch snapshot, all PR collisions, the candidate
510+
and final-head review, final release metadata and public documentation, and the
511+
exact tag/Release state, including removal of the one-cycle `last-release-sha`.
512+
Because workflow concurrency does not lock `main` or PR metadata against other
513+
actors, the maintainer must hold a short mutation freeze from the release-PR
514+
merge until the Release Please run reaches its post-action validation. Do not
515+
merge another `main` PR, edit the release PR, change its labels or review, or
516+
mutate the release branch during that window. The workflow repeats those checks
517+
after the action and stops publication on any drift, but it cannot delete or
518+
replace an immutable Release created during an external race.
491519

492520
The stale branch
493521
`release-please--branches--main--components--cometapi` at
@@ -546,6 +574,17 @@ PRs, and normal Release Please tag and GitHub Release creation. Repository
546574
Actions pull-request authorization is now enabled for that scoped job; the
547575
workflow still uses only its default token and job-local permissions.
548576
577+
The recovery rules follow the pinned implementation rather than assuming the
578+
action is atomic. Release Please 17.6.0
579+
[creates the Release before PR comments and label changes](https://github.com/googleapis/release-please/blob/712fcf01effd08d7b0e7b1fd3861f2cb388bc8d1/src/manifest.ts#L1258-L1319),
580+
while the pinned action emits release outputs only after that call returns. An
581+
unchanged release PR may also return
582+
[no PR result](https://github.com/googleapis/release-please/blob/712fcf01effd08d7b0e7b1fd3861f2cb388bc8d1/src/manifest.ts#L1089-L1101).
583+
Finally, commit-level `Release-As:` is rejected before the action because the
584+
[base strategy applies it before configured versioning](https://github.com/googleapis/release-please/blob/712fcf01effd08d7b0e7b1fd3861f2cb388bc8d1/src/strategies/base.ts#L543-L570).
585+
GitHub documents that a `GITHUB_TOKEN`-created PR's opened or synchronize event
586+
[creates an approval-required workflow run](https://github.com/github/docs/blob/e1e4aa937308f21c411c248b4966873536bb0cba/data/reusables/actions/actions-do-not-trigger-workflows.md#L1-L6).
587+
549588
## Stable 0.1.0 release evidence
550589

551590
Stable `0.1.0` completed on 2026-07-28 with these independently auditable

ROADMAP.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,13 @@ repository setting. Patch-only versioning prevents an implicit 0.2 bump during
116116
this maintenance window. The temporary anchor must be removed in that release
117117
PR before merge. The explicit component does not enter the public tag; the only
118118
accepted patch tag is `v0.1.1`. Publication is triggered from the successful
119-
first-attempt Release Please push run and independently verifies that run's
120-
exact release-created output artifact, default-branch commit, tag, immutable
121-
Release, and package artifact before the existing bounded live smoke and npm
122-
OIDC steps. The post-merge run also requires an administrator's approval on the
123-
release PR's final head.
119+
attempt-qualified Release Please push run and independently verifies that run's
120+
schema-v2 result artifact, default-branch commit, tag, immutable Release, and
121+
package artifact before the existing bounded live smoke and npm OIDC steps. A
122+
push rerun is bounded to the same run ID, SHA, candidate, Release-producing
123+
attempt, and exact Release state; manual preparation remains attempt-1-only.
124+
The Release notes must equal the reviewed `CHANGELOG` entry. The post-merge run also
125+
requires an administrator's approval on the release PR's final head.
124126

125127
The exact stale branch remains failure evidence until its contents, lack of an
126128
open PR, and lack of independent work are reconfirmed immediately before
@@ -282,8 +284,9 @@ component identity, patch versioning, pull-request configuration, one-cycle
282284
creation, and a trusted `workflow_run` handoff to the existing exact-artifact,
283285
bounded-live, and npm OIDC gates. Regression tests must reject stale manifest
284286
state, a 0.2 bump, missing PR configuration, an unrelated stale branch, hostile
285-
workflow events, reruns, mismatched action outputs, missing final-head approval,
286-
and declaration or runtime option bypasses.
287+
workflow events, unsafe or mismatched recovery attempts, mismatched action
288+
outputs, missing final-head approval, and declaration or runtime option
289+
bypasses.
287290

288291
Exit criteria:
289292

@@ -405,10 +408,13 @@ merge. Because the default `GITHUB_TOKEN` cannot cause a second workflow through
405408
a `release.published` event, `publish.yml` starts from successful Release Please
406409
workflow completion and re-establishes trust from exact repository state.
407410
Failed pull-request preparation runs are filtered out; any successful run
408-
without a run-bound `release_created` result, the exact Release Please-created
409-
tag, and immutable Release fails before live or registry access. Only a
410-
successful canonical first-attempt `push` run for the still-current exact
411-
`main` SHA can enter artifact verification.
411+
without a schema-v2 attempt-bound release result, the exact tag, and immutable
412+
Release fails before live or registry access. Only a successful canonical
413+
attempt-qualified `push` run for the still-current exact `main` SHA can enter
414+
artifact verification. A push retry may recover only the same run ID, SHA,
415+
candidate, and exact bot-authored immutable Release published inside one
416+
earlier Release Please step; manual preparation
417+
reruns remain forbidden.
412418

413419
Public Preview needs no registry workflow. Registry Alpha publishes from a
414420
human-reviewed immutable prerelease tag under the `next` dist-tag through OIDC

0 commit comments

Comments
 (0)