CI - Simplify PR approval check (#4578)

# Description of Changes

The previous version ended up incurring two different entries on the PR
(one for the `pull_request` event and one for the `pull_request_review`
event). Both versions were marked "required", so PRs could end up in an
unmergeable state if one check had succeeded but the other had failed
(e.g. if you submitted a PR approval, the previous `pull_request`
version of the check would still be failed since it didn't refresh).

See the entries at top and bottom here:
<img width="481" height="225" alt="image"
src="https://github.com/user-attachments/assets/5b7a4302-6bc2-47e9-93c8-812cb9ece60b"
/>

This PR fixes it by only allowing the `pull_request_review` events. I
_think_ this covers all the cases, but I'm not sure.

# API and ABI breaking changes

None.

# Expected complexity level and risk

1

# Testing

I don't know how to test it really 🤷

---------

Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
This commit is contained in:
Zeke Foppa
2026-03-06 19:12:49 -08:00
committed by GitHub
parent 21b5af0610
commit 154287c52f
+89 -28
View File
@@ -1,45 +1,106 @@
name: PR Approval Check
name: Review Checks
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened]
pull_request_review:
types: [submitted]
types: [submitted, dismissed]
merge_group:
permissions:
contents: read
pull-requests: read
statuses: write
concurrency:
group: pr-approval-check-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
check-approvals:
publish-approval-status:
name: Set approval status
runs-on: ubuntu-latest
# Skip this check if PR is in draft state
if: github.event.pull_request.draft == false
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Evaluate and publish approval status
uses: actions/github-script@v7
with:
fetch-depth: 0
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const contextName = "PR approval check";
- name: Check PR approvals
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Check if PR author is clockwork-labs-bot
if [[ "${{ github.event.pull_request.user.login }}" != "clockwork-labs-bot" ]]; then
echo "PR opened by ${{ github.event.pull_request.user.login }}, not clockwork-labs-bot. Skipping check."
exit 0
fi
let targetSha;
let state;
let description;
PR_NUMBER="${{ github.event.pull_request.number }}"
# Get approval count
APPROVALS=$(gh pr view $PR_NUMBER --json reviews -q '.reviews | map(select(.state == "APPROVED")) | length')
if (context.eventName === "merge_group") {
targetSha = process.env.GITHUB_SHA;
state = "success";
description = "Merge group entry; approvals already satisfied";
} else {
const pr = context.payload.pull_request;
targetSha = pr.head.sha;
echo "PR has $APPROVALS approvals"
if (pr.user.login !== "clockwork-labs-bot") {
state = "success";
description = "PR author is not clockwork-labs-bot";
} else {
const result = await github.graphql(
`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
latestOpinionatedReviews(first: 100, writersOnly: true) {
nodes {
state
author {
login
}
}
}
}
}
}
`,
{
owner: context.repo.owner,
repo: context.repo.repo,
number: pr.number,
}
);
if [[ $APPROVALS -lt 2 ]]; then
echo "Error: PRs from clockwork-labs-bot require at least 2 approvals"
exit 1
else
echo "PR has the required number of approvals"
fi
const effectiveApprovers =
result.repository.pullRequest.latestOpinionatedReviews.nodes
.filter((review) => review.state === "APPROVED")
.map((review) => review.author?.login)
.filter(Boolean);
core.info(
`Latest effective approvers (${effectiveApprovers.length}): ${effectiveApprovers.join(", ")}`
);
if (effectiveApprovers.length < 2) {
state = "failure";
description = "PRs from clockwork-labs-bot require at least 2 approvals";
} else {
state = "success";
description = "PR has the required number of approvals";
}
}
}
core.info(`Publishing status ${state} for ${targetSha}: ${description}`);
// We need to set a separate commit status for this, because it runs on both
// pull_request and pull_request_review events. If we don't set an explicit context,
// what happens is that there are sometimes two separate statuses on the same commit -
// one from each event type. This leads to weird cases where one copy of the check is failed,
// and the other is successful, and the failed one blocks the PR from merging.
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: targetSha,
state,
context: contextName,
description,
});