Skip to content

Commit d9d0a6f

Browse files
fix: CI runs on release PRs without a close and reopen (#23)
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 6bfcef6 commit d9d0a6f

4 files changed

Lines changed: 63 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,25 @@ A library rendered before the answers file existed needs one written by hand fir
7878
answers it was rendered with, `_src_path: gh:bedrock-python/python-library-template`,
7979
and as `_commit` the template commit it was rendered from, or the closest one.
8080

81+
## The release App
82+
83+
Release Please opens and updates its release PR with a token of an org-owned GitHub App.
84+
With the workflow's own `GITHUB_TOKEN`, the PR's CI run is held and never starts, the
85+
required "All checks passed" never reports, and the PR can only be merged after someone
86+
closes and reopens it. One App serves every library and is set up once:
87+
88+
1. Create a GitHub App owned by the org (org Settings → Developer settings → GitHub Apps):
89+
webhook off, repository permissions **Contents: Read and write** and **Pull requests:
90+
Read and write**, installable on this account only.
91+
2. Install it on the org, for all repositories.
92+
3. In org Settings → Secrets and variables → Actions, visible to all repositories:
93+
variable `RELEASE_PLEASE_APP_CLIENT_ID` = the App's Client ID, secret
94+
`RELEASE_PLEASE_APP_PRIVATE_KEY` = a private key generated for it.
95+
96+
Release PRs, tags and GitHub Releases are then authored by the App's bot account. While
97+
the variable is unset, the workflow falls back to `GITHUB_TOKEN` and its run carries a
98+
warning, and `setup_repo.py` warns for any repository that cannot see the two names.
99+
81100
## For AI Agents
82101

83102
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

@@ -111,6 +113,22 @@ def has_workflow(name: str) -> bool:
111113
gh(f"repos/{repo}/pages", "-X", "POST", payload={"build_type": "workflow"}, ok=(409,))
112114
pages = gh(f"repos/{repo}/pages", ok=(404,))
113115

116+
if has_workflow("release-please.yml"):
117+
# Without the App the release PR is opened with GITHUB_TOKEN and CI never
118+
# runs on it; the workflow falls back and warns, this says so up front.
119+
step("Release Please: the release App's client ID and private key reach the repository")
120+
def visible(kind: str) -> set[str]:
121+
names: set[str] = set()
122+
for scope in (f"organization-{kind}", kind):
123+
listed = gh(f"repos/{repo}/actions/{scope}?per_page=100", ok=(403, 404)) or {}
124+
names.update(item["name"] for item in listed.get(kind, []))
125+
return names
126+
127+
for kind, name in (("variables", RELEASE_APP_CLIENT_ID), ("secrets", RELEASE_APP_PRIVATE_KEY)):
128+
if name not in visible(kind):
129+
print(f" [WARN] Actions {kind[:-1]} {name} is not visible to {repo}: CI will not run on"
130+
" release PRs until it is (template README, 'The release App')")
131+
114132
step("Actions: workflows get a read-only token unless they ask for more; Release Please may open PRs")
115133
gh(f"repos/{repo}/actions/permissions/workflow", "-X", "PUT",
116134
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

‎template/NEW_LIBRARY_CHECKLIST.md.jinja‎

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

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

0 commit comments

Comments
 (0)