Skip to content

Commit e7af9cd

Browse files
committed
fix: CI runs on release PRs without a close and reopen
Release Please opened and updated its PR with GITHUB_TOKEN. GitHub holds the pull_request run such an event creates until it expires, so the required All checks passed never reported, and every release needed someone to close and reopen the PR after its last update. The workflow now mints a token from an org-owned GitHub App (actions/create-github-app-token, scoped to contents and pull-requests write, the same permissions the job's GITHUB_TOKEN has) and hands it to Release Please, so its PR events start CI like anyone else's. The App is configured once per org: RELEASE_PLEASE_APP_CLIENT_ID as a variable, RELEASE_PLEASE_APP_PRIVATE_KEY as a secret. Until then the workflow falls back to GITHUB_TOKEN and warns, so a new library still releases the way it does today. setup_repo.py warns when a repository cannot see the two names. Closes #12
1 parent 8ebf45d commit e7af9cd

4 files changed

Lines changed: 63 additions & 0 deletions

File tree

‎NEW_LIBRARY_CHECKLIST.md.jinja‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ This configures (idempotent, re-run any time):
5050
private vulnerability reporting
5151
- A `master` ruleset: pull requests only, no force-push or deletion, `All checks passed`
5252
required (left out with a warning if CI has not reported yet — re-run after it has)
53+
- A warning if the repository cannot see the release App's credentials: release PRs
54+
would then get no CI (template README, "The release App")
5355

5456
## Step 4 — PyPI Trusted Publisher (manual)
5557

‎README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ Then:
5959
4. Set up PyPI Trusted Publisher at pypi.org/manage/account/publishing/
6060
5. Add `CODECOV_TOKEN` secret in repo Settings → Secrets → Actions
6161

62+
## The release App
63+
64+
Release Please opens and updates its release PR with a token of an org-owned GitHub App.
65+
With the workflow's own `GITHUB_TOKEN`, the PR's CI run is held and never starts, the
66+
required "All checks passed" never reports, and the PR can only be merged after someone
67+
closes and reopens it. One App serves every library and is set up once:
68+
69+
1. Create a GitHub App owned by the org (org Settings → Developer settings → GitHub Apps):
70+
webhook off, repository permissions **Contents: Read and write** and **Pull requests:
71+
Read and write**, installable on this account only.
72+
2. Install it on the org, for all repositories.
73+
3. In org Settings → Secrets and variables → Actions, visible to all repositories:
74+
variable `RELEASE_PLEASE_APP_CLIENT_ID` = the App's Client ID, secret
75+
`RELEASE_PLEASE_APP_PRIVATE_KEY` = a private key generated for it.
76+
77+
Release PRs, tags and GitHub Releases are then authored by the App's bot account. While
78+
the variable is unset, the workflow falls back to `GITHUB_TOKEN` and its run carries a
79+
warning, and `setup_repo.py` warns for any repository that cannot see the two names.
80+
6281
## For AI Agents
6382

6483
See [`.claude/LIBRARY_CREATION.md`](.claude/LIBRARY_CREATION.md) for important rules:

‎scripts/setup_repo.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
RULESET_NAME = "master-rules"
2626
REQUIRED_CHECK = "All checks passed"
27+
RELEASE_APP_CLIENT_ID = "RELEASE_PLEASE_APP_CLIENT_ID" # organization variable
28+
RELEASE_APP_PRIVATE_KEY = "RELEASE_PLEASE_APP_PRIVATE_KEY" # organization secret
2729
MAX_TOPICS = 20
2830

2931

@@ -95,6 +97,22 @@ def has_workflow(name: str) -> bool:
9597
gh(f"repos/{repo}/pages", "-X", "POST", payload={"build_type": "workflow"}, ok=(409,))
9698
pages = gh(f"repos/{repo}/pages", ok=(404,))
9799

100+
if has_workflow("release-please.yml"):
101+
# Without the App the release PR is opened with GITHUB_TOKEN and CI never
102+
# runs on it; the workflow falls back and warns, this says so up front.
103+
step("Release Please: the release App's client ID and private key reach the repository")
104+
def visible(kind: str) -> set[str]:
105+
names: set[str] = set()
106+
for scope in (f"organization-{kind}", kind):
107+
listed = gh(f"repos/{repo}/actions/{scope}?per_page=100", ok=(403, 404)) or {}
108+
names.update(item["name"] for item in listed.get(kind, []))
109+
return names
110+
111+
for kind, name in (("variables", RELEASE_APP_CLIENT_ID), ("secrets", RELEASE_APP_PRIVATE_KEY)):
112+
if name not in visible(kind):
113+
print(f" [WARN] Actions {kind[:-1]} {name} is not visible to {repo}: CI will not run on"
114+
" release PRs until it is (template README, 'The release App')")
115+
98116
step("Actions: workflows get a read-only token unless they ask for more; Release Please may open PRs")
99117
gh(f"repos/{repo}/actions/permissions/workflow", "-X", "PUT",
100118
payload={"default_workflow_permissions": "read", "can_approve_pull_request_reviews": True})

‎template/.github/workflows/release-please.yml‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,31 @@ jobs:
1212
release-please:
1313
runs-on: ubuntu-latest
1414
steps:
15+
# A pull request opened or updated with GITHUB_TOKEN does not get a CI run
16+
# that ever starts, so the release PR could not pass its required check
17+
# until a person closed and reopened it. A token of the org's release App
18+
# starts CI like anyone else's push. Until the App is set up, GITHUB_TOKEN
19+
# is used and the run says so. Setup, once per org:
20+
# https://github.com/bedrock-python/python-library-template#the-release-app
21+
- name: Token from the release App
22+
id: app
23+
if: vars.RELEASE_PLEASE_APP_CLIENT_ID != ''
24+
uses: actions/create-github-app-token@v3
25+
with:
26+
client-id: ${{ vars.RELEASE_PLEASE_APP_CLIENT_ID }}
27+
private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }}
28+
permission-contents: write
29+
permission-pull-requests: write
30+
31+
- name: No release App
32+
if: steps.app.outcome == 'skipped'
33+
run: >-
34+
echo "::warning::RELEASE_PLEASE_APP_CLIENT_ID is not set, so the release PR
35+
is opened with GITHUB_TOKEN and CI will not run on it: close and reopen it
36+
right before merging."
37+
1538
- uses: googleapis/release-please-action@v5
1639
with:
40+
token: ${{ steps.app.outputs.token || github.token }}
1741
config-file: release-please-config.json
1842
manifest-file: .release-please-manifest.json

0 commit comments

Comments
 (0)