fix: skip dual approval check gracefully on dismissed reviews (#122)

* feat(release): add token permission pre-check

Detect missing write permissions early in the release pipeline rather
than failing late during git push with a cryptic 403 error (see PRI-348).

The new check-token-permissions job generates a GitHub App token and
attempts to create a test ref via the API. On 201 the token has write
permission (cleaned up immediately); on 403 the release job is skipped
with a clear error message. This saves CI time and provides actionable
diagnostics.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: skip dual approval check gracefully on dismissed reviews

When a pull_request_review event is dismissed, the PR context is null and
PR_NUMBER is empty. Instead of exiting with an error, exit 0 (skip) since
dismissed reviews are not approvals and do not affect the approval state.

Fixes PRI-314.

---------

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
privilegedescalation-engineer[bot]
2026-05-04 05:00:15 +00:00
committed by GitHub
parent d0cdad1922
commit 06e6784174
2 changed files with 44 additions and 4 deletions
+2 -2
View File
@@ -51,8 +51,8 @@ jobs:
REPO: ${{ github.repository }}
run: |
if [ -z "${PR_NUMBER}" ]; then
echo "::error::No pull request number found in event context. This workflow must be called from a pull_request or pull_request_review trigger."
exit 1
echo "::notice::No PR number in context (dismissed review?). Skipping dual approval check — no action needed."
exit 0
fi
echo "Checking approvals on PR #${PR_NUMBER} in ${REPO}"
+42 -2
View File
@@ -58,6 +58,46 @@ jobs:
with:
node-version: ${{ inputs.node-version }}
check-token-permissions:
needs: check-secrets
if: needs.check-secrets.outputs.ready == 'true'
runs-on: runners-privilegedescalation
outputs:
has_write: ${{ steps.check.outputs.has_write }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- name: Check write permissions via API
id: check
run: |
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/git/refs" \
-d '{"ref":"refs/heads/_release_check","sha":"${{ github.sha }}"}')
if [ "$HTTP_CODE" = "201" ]; then
echo "::notice::Token has write permission — cleaning up test ref."
curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
"https://api.github.com/repos/${{ github.repository }}/git/refs/heads/_release_check"
echo "has_write=true" >> $GITHUB_OUTPUT
elif [ "$HTTP_CODE" = "403" ]; then
echo "::error::Token lacks write permission. Release cannot push tags or branches."
echo "has_write=false" >> $GITHUB_OUTPUT
exit 1
else
echo "::warning::Unexpected response ($HTTP_CODE) when checking write permission."
echo "has_write=false" >> $GITHUB_OUTPUT
exit 1
fi
check-tag:
needs: check-secrets
if: needs.check-secrets.outputs.ready == 'true'
@@ -79,8 +119,8 @@ jobs:
fi
release:
needs: [ci, check-tag, check-secrets]
if: needs.check-secrets.outputs.ready == 'true' && needs.check-tag.outputs.skip != 'true'
needs: [ci, check-tag, check-secrets, check-token-permissions]
if: needs.check-secrets.outputs.ready == 'true' && needs.check-tag.outputs.skip != 'true' && needs.check-token-permissions.outputs.has_write == 'true'
runs-on: runners-privilegedescalation
timeout-minutes: 10