Skip to content

Auto-merge bump PRs whose commit is signed by a bump workflow - #15973

Merged
reaperhulk merged 5 commits into
mainfrom
claude/relaxed-dijkstra-sucsl0
Nov 7, 2026
Merged

reaperhulk merged 5 commits into
mainfrom
claude/relaxed-dijkstra-sucsl0

Conversation

@alex

@alex alex commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

This lets the daily bump PRs (BoringSSL/OpenSSL/AWS-LC, downstreams, x509-limbo/wycheproof) merge without a human, gated on a sigstore signature rather than on anything about the PR itself.

How it works

Signing (the bump workflows). Each bump job gets id-token: write and runs a new composite action, .github/actions/setup-gitsign. The action installs gitsign v0.18.0 from a release binary checked against a pinned SHA-256, and sets git to sign every commit with it (gpg.format=x509, commit.gpgsign=true, gitsign.tokenProvider=github-actions, gitsign.rekorMode=offline). peter-evans/create-pull-request makes its commits with plain git commit, so it picks up this config with no other changes. Each commit is signed with a short-lived Fulcio certificate whose SAN is the workflow's identity, for example:

https://github.com/pyca/cryptography/.github/workflows/downstream-version-bump.yml@refs/heads/main

The signature is logged in Rekor. Offline mode puts the inclusion proof inside the signature, so verifying it doesn't need a Rekor lookup.

Verifying and merging (auto-merge-bumps.yml + .github/bin/auto_merge_bump_prs.py). This runs when any bump workflow finishes (workflow_run) and on manual dispatch (which has a dry-run input). It only runs code from main. For each open, non-draft PR against main that comes from a branch in this repo:

  1. It requires exactly one commit, and that commit must be the PR head. Because the PR's only commit is the signed one, its parent is already on main.
  2. It fetches that commit and runs gitsign verify --certificate-identity=<one of the 3 workflow identities> --certificate-oidc-issuer=https://token.actions.githubusercontent.com.
  3. If verification passes, it approves as github-actions[bot] (GITHUB_TOKEN), with the review pinned to that commit. pyca-boringbot can't do this because it authors the PRs, and GitHub won't let you approve your own PR.
  4. It then enables squash auto-merge as pyca-boringbot (app token), with --match-head-commit=<verified sha>. It uses the app token because a merge done by GITHUB_TOKEN wouldn't trigger the push CI on main. The token has workflows: write because the bump PRs edit ci.yml.

Both steps are idempotent: if a PR already has an approval from us for that head commit, or already has auto-merge enabled, the step is skipped. The script doesn't look at the PR's author, title or branch, only at the signature.

Repo settings needed before this does anything

  • Allow auto-merge (Settings → General).
  • Allow GitHub Actions to create and approve pull requests (Settings → Actions → General; the org may need to allow it as well). Without it, the approval call fails.
  • Recommended: dismiss stale approvals when new commits are pushed on main's branch protection or ruleset. Otherwise, someone with write access could push to an approved bump branch after verification and before the merge.

Testing

  • I ran auto_merge_bump_prs.py against stubbed gh/git/gitsign. It handled each case correctly: signed PR, unsigned PR, bad signature, fork, already approved and auto-merge already on. It also used the right token for each call.
  • nox -e flake passes.
  • Not tested end to end: signing with a real OIDC token can only happen in Actions. After merging, the next bump run should produce a signed commit. Running "Auto-merge bump PRs" with dry-run will then show whether verification passes, before anything gets approved.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TKUFHFE7g1LPHJakSrKgBm

The bump workflows now sign their commits with gitsign, using their
GitHub Actions OIDC identity (a Fulcio certificate whose SAN is the
workflow file on main, logged to Rekor).

A new workflow looks for open PRs whose only commit is the PR head and
carries a valid gitsign signature from one of those workflow identities.
For such PRs it approves (as github-actions[bot]) and enables squash
auto-merge (as pyca-boringbot, so the merge triggers push workflows),
pinned to the verified head commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TKUFHFE7g1LPHJakSrKgBm
OIDC identity (requires `id-token: write`).

inputs:
sign-commits:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this an option that defaults to false?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no good reason for it. It only existed because the auto-merge job needs the gitsign binary to run gitsign verify, but it never commits. Turning signing on in that job does nothing, so I've removed the input and the action now always configures signing (7643bbe). I also dropped the id-token precheck: if the permission is missing, gitsign fails on its own at commit time.


Generated by Claude Code

The auto-merge job never commits, so configuring signing there is
harmless, and it removes an option whose default didn't make sense.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TKUFHFE7g1LPHJakSrKgBm
Comment thread .github/workflows/auto-merge-bumps.yml Outdated
description: "Only report what would be done"
type: boolean
default: false
schedule:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no cron needed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in eb3eafc. It now runs only on workflow_run (when a bump workflow finishes) and workflow_dispatch.


Generated by Claude Code

schedule:
- cron: "37 */4 * * *"
workflow_run:
workflows:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reference these by filename?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not: on.workflow_run.workflows only matches against the workflow's name:. The docs say the filters "match more than one workflow name", and every example uses names. I added a comment saying that, and pointing to BUMP_WORKFLOWS in the script, which does use the filenames, since those are what end up in the signing identity (b96ac71).

If a workflow gets renamed and this list goes stale, the result is just a missed auto-merge, so a person merges it by hand. It can't cause anything to be approved that shouldn't be.


Generated by Claude Code

Comment thread .github/workflows/auto-merge-bumps.yml Outdated
type: boolean
default: false
schedule:
- cron: "37 */4 * * *"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't do a fallback like this. If we miss one the worst case is a person manually merges, so just remove it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The cron fallback is gone (eb3eafc), so it now runs only on workflow_run and workflow_dispatch.


Generated by Claude Code

GH_TOKEN: ${{ github.token }}
# Auto-merge is enabled by pyca-boringbot, so that the eventual
# merge triggers `push` workflows (which GITHUB_TOKEN would not).
AUTO_MERGE_TOKEN: ${{ steps.generate-token.outputs.token }}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not do this with teh regular job token?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, but there's a cost. With auto-merge, the merge is attributed to whoever enabled it. If that's GITHUB_TOKEN (github-actions[bot]), the resulting push to main won't trigger workflows, so ci.yml and wheel-builder.yml wouldn't run their on: push jobs for these merges. That's the documented "events triggered by GITHUB_TOKEN don't start new workflow runs" behavior.

There may be a second problem I haven't verified: GITHUB_TOKEN can't be granted the workflows permission, and two of the three bump PRs edit .github/workflows/ci.yml. That could block the merge entirely.

If you're fine with no push-CI on main for bump merges, I'll switch to GITHUB_TOKEN. That drops the app-token step and keeps the boringbot secrets out of this job entirely. Want me to?


Generated by Claude Code

GITSIGN_SHA256: "2bc625472a08fea3bb6a57982c7d8b4978d1032251d305ceef3bf2ded0ca4483"
run: |
set -euo pipefail
if [ "${RUNNER_OS}" != "Linux" ] || [ "${RUNNER_ARCH}" != "X64" ]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only run these jobs on x86-64 we don't need this branch

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 8d52e33.


Generated by Claude Code

@reaperhulk
reaperhulk merged commit 5bdd200 into main Nov 7, 2026
66 checks passed
@reaperhulk
reaperhulk deleted the claude/relaxed-dijkstra-sucsl0 branch November 7, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants