From 61d7d24a900bd5319599b5069d93f563b907587d Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 3 Jul 2026 10:57:24 +0900 Subject: [PATCH 1/3] Rework approval bypass notifier to work on push events --- .github/workflows/notify-approval-bypass.yaml | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/.github/workflows/notify-approval-bypass.yaml b/.github/workflows/notify-approval-bypass.yaml index c984990..360916e 100644 --- a/.github/workflows/notify-approval-bypass.yaml +++ b/.github/workflows/notify-approval-bypass.yaml @@ -10,33 +10,66 @@ jobs: approval: runs-on: ubuntu-latest steps: - - name: Fail If No Approval - if: ${{ github.event.pull_request.merged }} + - name: Find merged pull request for commit + id: pr + env: + GH_TOKEN: ${{ github.token }} + run: | + pr_json="$( + gh api --paginate \ + -H "Accept: application/vnd.github+json" \ + "/repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls?per_page=100" \ + | jq -s 'add | map(select(.merged_at != null)) | sort_by(.merged_at) | last // empty' + )" + + if [[ -z "${pr_json}" ]]; then + echo "found=false" >> "${GITHUB_OUTPUT}" + echo "No merged pull request associated with ${GITHUB_SHA}; skipping approval check." + exit 0 + fi + + pr_number="$(jq -r '.number' <<<"${pr_json}")" + pr_url="$(jq -r '.html_url' <<<"${pr_json}")" + + echo "found=true" >> "${GITHUB_OUTPUT}" + echo "number=${pr_number}" >> "${GITHUB_OUTPUT}" + echo "url=${pr_url}" >> "${GITHUB_OUTPUT}" + + - name: Check approval + id: approval + if: ${{ steps.pr.outputs.found == 'true' }} env: - AUTH_HEADER: 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' - JSON_HEADER: 'Content-Type: application/json' - REVIEWS_URL: 'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews' - REVIEWS_PER_PAGE: 100 + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ steps.pr.outputs.number }} + PR_URL: ${{ steps.pr.outputs.url }} run: | - TEMP="$(mktemp -d)" - for i in $(seq 1 100) - do - curl -sSL -H "${JSON_HEADER}" -H "${AUTH_HEADER}" "${REVIEWS_URL}?per_page=${REVIEWS_PER_PAGE}&page=$i" > "$TEMP/p${i}.json" - if [[ $i != 1 ]] - then - jq -s '.[0] + .[1]' "$TEMP/p$((i - 1))merge.json" "$TEMP/p${i}.json" > "$TEMP/p${i}merge.json" - else - cp "$TEMP/p${i}.json" "$TEMP/p${i}merge.json" - fi - if [[ $(cat "$TEMP/p${i}.json" | jq length) != ${REVIEWS_PER_PAGE?} ]]; then break; fi - done - cat "$TEMP/p${i}merge.json" | jq -e '. | map({user: .user.login, state: .state}) - | map(select(.state == "APPROVED" or .state == "CHANGES_REQUESTED" or .state == "DISMISSED")) - | reduce .[] as $x ({}; .[$x.user] = $x.state) - | to_entries | map(.value) - | contains(["APPROVED"]) and (contains(["CHANGES_REQUESTED"]) | not)' + has_approval="$( + gh api --paginate \ + -H "Accept: application/vnd.github+json" \ + "/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/reviews?per_page=100" \ + | jq -s 'add + | map(select(.state == "APPROVED" or .state == "CHANGES_REQUESTED" or .state == "DISMISSED")) + | reduce .[] as $review ({}; .[$review.user.login] = $review.state) + | [.[]] as $latest_states + | (($latest_states | index("APPROVED")) != null and (($latest_states | index("CHANGES_REQUESTED")) == null))' + )" + + echo "approved=${has_approval}" >> "${GITHUB_OUTPUT}" + if [[ "${has_approval}" == "true" ]]; then + echo "Pull request ${PR_URL} had an active approval." + else + echo "Pull request ${PR_URL} was merged without an active approval." + fi + - name: Slack Notification - if: ${{ failure() }} + if: ${{ steps.approval.outputs.approved == 'false' }} + env: + PR_URL: ${{ steps.pr.outputs.url }} + SLACK_WEBHOOK: ${{ secrets.SLACK_MERGE_WITHOUT_APPROVAL_WEBHOOK }} run: | - jq --null-input '{ text: "Oh no! The following PR was merged without approval: ${{github.event.pull_request.html_url}}" }' \ - | curl -sSL -X POST -H 'Content-Type: application/json' -d @- '${{ secrets.SLACK_MERGE_WITHOUT_APPROVAL_WEBHOOK }}' + jq --null-input --arg url "${PR_URL}" '{ text: "Oh no! The following PR was merged without approval: \($url)" }' \ + | curl -sSL -X POST -H 'Content-Type: application/json' -d @- "${SLACK_WEBHOOK}" + + - name: Fail If No Approval + if: ${{ steps.approval.outputs.approved == 'false' }} + run: exit 1 From 00a1a871c6a53ea73f54c39e3d3e85a648a95b2c Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Tue, 7 Jul 2026 17:07:26 +0900 Subject: [PATCH 2/3] Allow migration --- .github/workflows/notify-approval-bypass.yaml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/notify-approval-bypass.yaml b/.github/workflows/notify-approval-bypass.yaml index 360916e..dba7649 100644 --- a/.github/workflows/notify-approval-bypass.yaml +++ b/.github/workflows/notify-approval-bypass.yaml @@ -8,6 +8,7 @@ permissions: pull-requests: read jobs: approval: + if: ${{ github.event_name != 'pull_request' }} runs-on: ubuntu-latest steps: - name: Find merged pull request for commit @@ -73,3 +74,39 @@ jobs: - name: Fail If No Approval if: ${{ steps.approval.outputs.approved == 'false' }} run: exit 1 + + # TODO: Remove after all repos move from pull_request event to push event. + approval-pr: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - name: Fail If No Approval + if: ${{ github.event.pull_request.merged }} + env: + AUTH_HEADER: 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' + JSON_HEADER: 'Content-Type: application/json' + REVIEWS_URL: 'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews' + REVIEWS_PER_PAGE: 100 + run: | + TEMP="$(mktemp -d)" + for i in $(seq 1 100) + do + curl -sSL -H "${JSON_HEADER}" -H "${AUTH_HEADER}" "${REVIEWS_URL}?per_page=${REVIEWS_PER_PAGE}&page=$i" > "$TEMP/p${i}.json" + if [[ $i != 1 ]] + then + jq -s '.[0] + .[1]' "$TEMP/p$((i - 1))merge.json" "$TEMP/p${i}.json" > "$TEMP/p${i}merge.json" + else + cp "$TEMP/p${i}.json" "$TEMP/p${i}merge.json" + fi + if [[ $(cat "$TEMP/p${i}.json" | jq length) != ${REVIEWS_PER_PAGE?} ]]; then break; fi + done + cat "$TEMP/p${i}merge.json" | jq -e '. | map({user: .user.login, state: .state}) + | map(select(.state == "APPROVED" or .state == "CHANGES_REQUESTED" or .state == "DISMISSED")) + | reduce .[] as $x ({}; .[$x.user] = $x.state) + | to_entries | map(.value) + | contains(["APPROVED"]) and (contains(["CHANGES_REQUESTED"]) | not)' + - name: Slack Notification + if: ${{ failure() }} + run: | + jq --null-input '{ text: "Oh no! The following PR was merged without approval: ${{github.event.pull_request.html_url}}" }' \ + | curl -sSL -X POST -H 'Content-Type: application/json' -d @- '${{ secrets.SLACK_MERGE_WITHOUT_APPROVAL_WEBHOOK }}' \ No newline at end of file From c7bb646c4b69b6a72d80fab4ed675f2b127a20ad Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Tue, 7 Jul 2026 17:14:35 +0900 Subject: [PATCH 3/3] newline --- .github/workflows/notify-approval-bypass.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/notify-approval-bypass.yaml b/.github/workflows/notify-approval-bypass.yaml index dba7649..49dc1ee 100644 --- a/.github/workflows/notify-approval-bypass.yaml +++ b/.github/workflows/notify-approval-bypass.yaml @@ -109,4 +109,4 @@ jobs: if: ${{ failure() }} run: | jq --null-input '{ text: "Oh no! The following PR was merged without approval: ${{github.event.pull_request.html_url}}" }' \ - | curl -sSL -X POST -H 'Content-Type: application/json' -d @- '${{ secrets.SLACK_MERGE_WITHOUT_APPROVAL_WEBHOOK }}' \ No newline at end of file + | curl -sSL -X POST -H 'Content-Type: application/json' -d @- '${{ secrets.SLACK_MERGE_WITHOUT_APPROVAL_WEBHOOK }}'