Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Full detail on the regions: [The standing-PR body is an interface](./standing-pr
**Agent proposes → human approves → CI publishes.**

- An agent contributes the way anyone else does: a feeder PR, or an edit to the standing PR's notes region. It should never hold a registry credential.
- The **merge is the approval**, and a review-required ruleset on the base branch (`main`) is what enforces it. This is the actual gate — not the instructions above, which are advisory. [Branch rulesets](../packages/release/docs/ci-setup.md#branch-rulesets-the-merge-gate) covers setting it up, including why it should require code-owner review rather than "≥ 1 approval" alone, and locking `release/next` so commits can't be pushed into the release.
- The **merge is the approval**, and a review-required ruleset on the base branch (`main`) is what enforces it. This is the actual gate — not the instructions above, which are advisory. [Branch rulesets](../packages/release/docs/ci-setup.md#branch-rulesets-the-merge-gate) covers setting it up and locking `release/next`. [Workflow token approvals](../packages/release/docs/ci-setup.md#workflow-token-approvals) covers why it should require code-owner review rather than "≥ 1 approval" alone, and why a bypass for the Actions bot opens both rulesets to every workflow.
- **CI publishes**, using short-lived OIDC tokens where the registry supports them — npm does, and so does pub.dev for workflow runs its automated-publishing settings accept (it limits which events may publish). See [OIDC setup](../packages/release/docs/ci-setup.md#npm-oidc-trusted-publishing-recommended) and [Dart / pub.dev](./dart.md#auth).

The property that matters most — nothing an agent does reaches a registry without a human merge — holds only while the agent can't get around that merge:
Expand Down
3 changes: 2 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ Optional guardrails mirror changesets' workflow:

5. **Enable Actions write access.** Go to your repository Settings > Actions > General and
enable "Allow GitHub Actions to create and approve pull requests". This is required for
the bot to manage the release PR.
the bot to manage the release PR. It also lets workflow tokens approve pull requests: see
[Workflow token approvals](../packages/release/docs/ci-setup.md#workflow-token-approvals).

6. **Remove changeset files.** Delete `.changeset/*.md` and `.changeset/config.json`.
Existing changelog text should be retained — see the stumbling blocks section below.
Expand Down
2 changes: 1 addition & 1 deletion docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ This gives you an honest choice between the two recovery paths. The warning is k

**Cause:** The branch has protection rules that block direct pushes. This can affect both the version commit push and the tag push.

**Fix:** Check whether the configured `publish.git.pushMethod` matches the protection ruleset. For GitHub Actions with a bot token, `pushMethod: "https"` is usually required (not SSH). If a bypass is needed, configure the `github-actions[bot]` app as a bypass actor in the branch protection settings rather than disabling protection. Note: `git.skipHooks: true` bypasses local git hooks but has no effect on remote branch protection — these are independent concerns.
**Fix:** Check whether the configured `publish.git.pushMethod` matches the protection ruleset. For GitHub Actions with a bot token, `pushMethod: "https"` is usually required (not SSH). If a bypass is needed, configure the `github-actions[bot]` app as a bypass actor in the branch protection settings rather than disabling protection. That bypass applies to every workflow in the repo, not just the release job: see [Workflow token approvals](../packages/release/docs/ci-setup.md#workflow-token-approvals). Note: `git.skipHooks: true` bypasses local git hooks but has no effect on remote branch protection — these are independent concerns.

---

Expand Down
3 changes: 2 additions & 1 deletion examples/ci/standing-pr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ Needs `notes.releaseNotes.llm` configured and the matching LLM secret uncommente
permissions -> enable **"Allow GitHub Actions to create and approve pull
requests"**. Without it the first `update` run fails with HTTP 403. The
`pull-requests: write` workflow permission is necessary but **not** sufficient
— this toggle is separate.
— this toggle is separate. It also lets any workflow token approve pull
requests: see [Workflow token approvals](../../../packages/release/docs/ci-setup.md#workflow-token-approvals).
- `release/next` is **not** branch-protected (the bot force-pushes it), or the
bot has bypass.
- Squash merge produces a single `chore: release ...` commit on `main`, which
Expand Down
3 changes: 3 additions & 0 deletions examples/ci/standing-pr/standing-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# Workflow permissions -> enable "Allow GitHub Actions to create and approve
# pull requests". Without it the first `standing-pr update` fails with HTTP 403;
# the pull-requests: write permission below is necessary but not sufficient.
# The toggle also lets any workflow token approve PRs. Before relying on a
# "≥ 1 approval" ruleset, see
# https://github.com/goosewobbler/releasekit/blob/main/packages/release/docs/ci-setup.md#workflow-token-approvals
name: Standing Release PR

on:
Expand Down
19 changes: 16 additions & 3 deletions packages/release/docs/ci-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ POST /repos/owner/repo/pulls - 403
GitHub Actions is not permitted to create or approve pull requests.
```

The `pull-requests: write` permission in the workflow is necessary but not sufficient — this is a separate org/repo-level toggle.
The `pull-requests: write` permission in the workflow is necessary but not sufficient — this is a separate org/repo-level toggle. The same toggle also lets workflow tokens approve pull requests: see [Workflow token approvals](#workflow-token-approvals).

**Workflow permissions** (in the workflow YAML):

Expand Down Expand Up @@ -428,14 +428,27 @@ The in-code gates keep the *manifest* clean, but the merge itself is a GitHub ac

> **Rulesets require the right plan.** They're available on **public** repositories on any plan, and on **private** repositories only with **GitHub Pro / Team / Enterprise** — a private repo on Free has no rulesets at all. The **evaluate** (dry-run) enforcement mode mentioned below is **Enterprise-only**.

**1. Lock the release branch** — target `release/next` (your `ci.standingPr.branch`). Enable **Restrict creations**, **Restrict updates**, and **Restrict deletions** so only bypass actors can write the branch — no one can inject a commit that would then be published.
**1. Lock the release branch** — target `release/next` (your `ci.standingPr.branch`). Enable **Restrict creations**, **Restrict updates**, and **Restrict deletions** so only bypass actors can write the branch — no one else can inject a commit that would then be published. If the bypass actor is the Actions bot, that includes every workflow: see [Workflow token approvals](#workflow-token-approvals).

> ⚠️ The release bot pushes (and force-pushes) this branch, so it **must** be on the ruleset's *Bypass list* or releases break — ReleaseKit can't infer the bot's identity for you. **Add the bot as a bypass actor before the ruleset goes `active`.** On **GitHub Enterprise** you can instead create it in **evaluate** (dry-run) mode first and confirm in the repo's *rule insights* that the bot isn't tripped, then switch to active — but **evaluate mode is Enterprise-only**, so on every other plan add the bypass actor first and create the ruleset directly as `active`. (A bypass actor's force-push is not blocked by the lock, so the standing-PR refresh keeps working.)

**2. Require review on the default branch** — target `main` (or `~DEFAULT_BRANCH`). Enable **Require a pull request before merging** (≥ 1 approval), **Block force pushes**, and **Restrict deletions**. Since merging the standing PR is the publish, this gates the publish behind review. The standing-PR template needs *Allow GitHub Actions to create and approve pull requests*, which also lets workflow tokens approve pull requests — so require code-owner or team review rather than relying on "≥ 1 approval" alone ([#653](https://github.com/goosewobbler/releasekit/issues/653)).
**2. Require review on the default branch** — target `main` (or `~DEFAULT_BRANCH`). Enable **Require a pull request before merging** (≥ 1 approval), **Block force pushes**, and **Restrict deletions**. Since merging the standing PR is the publish, this gates the publish behind review. Don't rely on "≥ 1 approval" alone: see [Workflow token approvals](#workflow-token-approvals) for the settings to add.

**Mapping `allowedActors` to ruleset bypass actors:** a `@org/team-slug` entry maps to a **Team** bypass actor and a plain **username** maps to a **User** bypass actor — add either to the ruleset's *Bypass list*. (GitHub also exposes repository **roles** and **Apps**, which is how a bot running as a GitHub App or the Actions bot is exempted.) Always keep org/repo admins on the bypass list so you can't lock yourself out.

#### Workflow token approvals

The template creates the standing PR with `GITHUB_TOKEN`, which needs **Allow GitHub Actions to create and approve pull requests**, and that one toggle covers approving too. With it on, **any** workflow's `GITHUB_TOKEN` can approve a pull request, and that approval counts toward ruleset 2's "≥ 1 approval" for any PR that `github-actions[bot]` didn't author. A write collaborator could push a branch whose workflow approves their own feeder PR. Once it merges, it ships in the next standing release, whose diff shows only version and changelog updates, or straight away if it carries `release:immediate`.

So in ruleset 2, also enable:

- **Require review from Code Owners.** A workflow token isn't a code owner. The rule only covers paths in `CODEOWNERS`, so give it a `*` rule naming an owner with write access, such as `* @acme/maintainers`. A later pattern overrides `*` for its paths, so every pattern needs such an owner.
- **Dismiss stale pull request approvals when new commits are pushed**, so a code owner's approval doesn't carry over to commits pushed after it. Each standing-PR update, including the hourly scheduled run, pushes a new commit and can dismiss the standing PR's approval too, so approve it just before merging.

None of this applies to a ruleset's bypass actors. With the template's `GITHUB_TOKEN`, the release bot is the Actions bot, and a bypass for it is open to every workflow. Ruleset 1 needs one for the release branch. The template's `immediate-release` job pushes straight to `main`, so it needs one on ruleset 2 as well, which voids the settings above. Remove that job if you rely on them.

To keep registry credentials away from workflows on other branches, publish from a deployment environment restricted to the default branch. A merged PR's `closed` run has the branch it merged into as its ref, so the publish jobs it triggers pass the restriction. Name the environment on each of them, move registry tokens such as `NPM_TOKEN` to environment secrets, and require the environment in each registry's trusted-publisher settings. GitHub documents that ref only for the `closed` run, so a label-triggered job such as the example's `retry-publish` may be rejected.

#### Token caveats

- **`@org/team` allow-lists** need a token with **`read:org`** scope — a PAT or GitHub App, **not** the default `GITHUB_TOKEN`. Without it, the team-membership check can't be answered, and the two gate types resolve it differently: the **selection and release-label gates fail _closed_** (the actor isn't treated as authorized; their edit is reverted, with a warning), while the **publish-author gate (`enforceMergeAuthor`) fails _open_** (it warns and proceeds, because the branch ruleset is the primary merge gate — a token misconfiguration shouldn't block a legitimate release). So **don't rely on `enforceMergeAuthor` alone for team-based merge control** — set up the branch ruleset. Username and permission-threshold entries work with the default token.
Expand Down
3 changes: 3 additions & 0 deletions templates/workflows/standing-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# Without it, the first `standing-pr update` run will fail with HTTP 403.
# See https://github.com/goosewobbler/releasekit/blob/main/packages/release/docs/ci-setup.md#standing-release-pr
#
# The same toggle lets any workflow token approve PRs. Before relying on a
# "≥ 1 approval" ruleset, see https://github.com/goosewobbler/releasekit/blob/main/packages/release/docs/ci-setup.md#workflow-token-approvals
#
# Tip: exempt this PR's branch (release/*) from your build/test CI, or every update
# re-runs your whole matrix. See https://github.com/goosewobbler/releasekit/blob/main/packages/release/docs/ci-setup.md#keeping-ci-off-the-standing-pr

Expand Down
Loading