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 .github/workflows/pr-merge-gatekeeper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
uses: upsidr/merge-gatekeeper@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ignored: "require-review / check-approvals"
ignored: "check-approvals"
92 changes: 90 additions & 2 deletions .github/workflows/pr-require-bot-approvals.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# This is an inlined copy of
# censys/shared-actions/.github/workflows/reusable-require-bot-pr-approvals.yaml
# because this repository is public and cannot reference the private
# shared-actions repository. Keep the two in sync when either changes.
#
# The reusable workflow's inputs are resolved here to the values this repo
# needs; the censys-org runner setup is dropped since we run on ubuntu-latest,
# where the GitHub CLI is preinstalled.
name: PR - Require Bot Approvals

on:
Expand All @@ -7,5 +15,85 @@ on:
types: [ submitted, dismissed ]

jobs:
require-review:
uses: censys/shared-actions/.github/workflows/reusable-require-bot-pr-approvals.yaml@main
check-approvals:
runs-on: ubuntu-latest
permissions:
pull-requests: read
statuses: write
env:
# Comma-separated list of GitHub logins that require the extra approval count.
BOT_LOGINS: 'claude[bot]'
# Number of approving reviews required on PRs opened by one of BOT_LOGINS.
REQUIRED_APPROVALS: 2
# Name of the commit status this workflow sets to reflect the actual
# gating decision (success/pending). This exact name is what branch
# protection must require - the job itself always succeeds.
STATUS_CONTEXT: 'Additional Reviewers Required'
steps:
# This step always exits 0 - it never blocks merge directly. It only
# computes the gating decision; the actual required-status-check is the
# commit status set by the "Set commit status" step below, which is what
# branch protection should be configured against (see STATUS_CONTEXT).
- name: Check approving review count
id: check
env:
GITHUB_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
run: |
set -euo pipefail

is_gated_author=false
IFS=',' read -ra BOTS <<< "$BOT_LOGINS"
for bot in "${BOTS[@]}"; do
bot_trimmed="$(echo "$bot" | xargs)"
if [ "$bot_trimmed" = "$PR_AUTHOR" ]; then
is_gated_author=true
break
fi
done

if [ "$is_gated_author" != "true" ]; then
echo "PR author '$PR_AUTHOR' is not in the gated BOT_LOGINS list ($BOT_LOGINS) - no extra approvals required."
echo "state=success" >> "$GITHUB_OUTPUT"
echo "description=PR author is not a gated bot login; no extra approvals required." >> "$GITHUB_OUTPUT"
exit 0
fi

approval_count=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --paginate | \
jq '[.[] | {user: .user.login, state: .state, id: .id}] | group_by(.user) | map(max_by(.id)) | map(select(.state == "APPROVED")) | length')

if ! [[ "$approval_count" =~ ^[0-9]+$ ]]; then
echo "::error::Could not determine approving review count for PR #$PR_NUMBER (got '$approval_count'); failing closed via pending status."
echo "state=pending" >> "$GITHUB_OUTPUT"
echo "description=Could not determine approving review count; treating as not yet satisfied." >> "$GITHUB_OUTPUT"
exit 0
fi

echo "PR #$PR_NUMBER opened by '$PR_AUTHOR' currently has $approval_count approving review(s); $REQUIRED_APPROVALS required."

if [ "$approval_count" -lt "$REQUIRED_APPROVALS" ]; then
echo "state=pending" >> "$GITHUB_OUTPUT"
echo "description=$approval_count/$REQUIRED_APPROVALS approvals required." >> "$GITHUB_OUTPUT"
else
echo "state=success" >> "$GITHUB_OUTPUT"
echo "description=Required approvals met ($approval_count/$REQUIRED_APPROVALS)." >> "$GITHUB_OUTPUT"
fi

- name: Set commit status
uses: actions/github-script@v7
env:
STATE: ${{ steps.check.outputs.state }}
DESCRIPTION: ${{ steps.check.outputs.description }}
with:
script: |
const sha = context.payload.pull_request.head.sha;
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha,
state: process.env.STATE,
context: process.env.STATUS_CONTEXT,
description: process.env.DESCRIPTION,
target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
});
Loading