From 06e67841749d6028de03b60bccb3d4d5225f0af5 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-engineer[bot]" <269729446+privilegedescalation-engineer[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 05:00:15 +0000 Subject: [PATCH] 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 * 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 Co-authored-by: Paperclip --- .github/workflows/dual-approval-check.yaml | 4 +- .github/workflows/plugin-release.yaml | 44 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dual-approval-check.yaml b/.github/workflows/dual-approval-check.yaml index 39453c8..5127c64 100644 --- a/.github/workflows/dual-approval-check.yaml +++ b/.github/workflows/dual-approval-check.yaml @@ -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}" diff --git a/.github/workflows/plugin-release.yaml b/.github/workflows/plugin-release.yaml index 480a70a..785d02f 100644 --- a/.github/workflows/plugin-release.yaml +++ b/.github/workflows/plugin-release.yaml @@ -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