Auto Approve #513
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto Approve | |
| on: | |
| # Trigger when check runs complete | |
| check_suite: | |
| types: [completed] | |
| # Also trigger on workflow run completion for reusable workflows | |
| workflow_run: | |
| workflows: ["PR Tests"] | |
| types: [completed] | |
| # Trigger when Copilot (or any reviewer) submits a review | |
| pull_request_review: | |
| types: [submitted] | |
| permissions: read-all | |
| jobs: | |
| auto-approve: | |
| runs-on: ubuntu-latest | |
| # Only run on pull requests, not pushes | |
| if: | | |
| github.event.check_suite.pull_requests[0] != null || | |
| github.event.workflow_run.pull_requests[0] != null || | |
| github.event.pull_request != null | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Get PR number | |
| id: pr | |
| run: | | |
| if [ "${{ github.event_name }}" == "check_suite" ]; then | |
| PR_NUMBER="${{ github.event.check_suite.pull_requests[0].number }}" | |
| elif [ "${{ github.event_name }}" == "pull_request_review" ]; then | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| else | |
| PR_NUMBER="${{ github.event.workflow_run.pull_requests[0].number }}" | |
| fi | |
| echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "PR number: $PR_NUMBER" | |
| - name: Check required statuses | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PR_NUMBER="${{ steps.pr.outputs.number }}" | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "No PR number found, skipping" | |
| echo "should_approve=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Get PR head SHA | |
| HEAD_SHA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER --jq '.head.sha') | |
| echo "Head SHA: $HEAD_SHA" | |
| # Check Copilot review - must verify Copilot has actually reviewed first | |
| # Copilot leaves "COMMENTED" reviews (never "APPROVED" or "CHANGES_REQUESTED") | |
| # We need to: 1) Confirm a review exists, 2) Check for UNRESOLVED comments | |
| # Note: Copilot uses "copilot-pull-request-reviewer[bot]" for reviews | |
| COPILOT_REVIEW=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --jq '[.[] | select(.user.login == "copilot-pull-request-reviewer[bot]")] | length') | |
| # Use GraphQL to count unresolved Copilot review threads | |
| REPO_OWNER="${{ github.repository_owner }}" | |
| REPO_NAME="${{ github.repository }}" | |
| REPO_NAME="${REPO_NAME#*/}" # Extract repo name from owner/repo | |
| UNRESOLVED_THREADS=$(gh api graphql -f query="query { repository(owner: \"$REPO_OWNER\", name: \"$REPO_NAME\") { pullRequest(number: $PR_NUMBER) { reviewThreads(first: 100) { nodes { isResolved comments(first: 1) { nodes { author { login } } } } } } } }" \ | |
| --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .comments.nodes[0].author.login == "copilot-pull-request-reviewer")] | length') | |
| COPILOT_OK="false" | |
| if [ "$COPILOT_REVIEW" -eq 0 ]; then | |
| echo "Copilot review: Not yet reviewed (no review found)" | |
| elif [ "$UNRESOLVED_THREADS" -eq 0 ]; then | |
| echo "Copilot review: Reviewed with no unresolved issues" | |
| COPILOT_OK="true" | |
| else | |
| echo "Copilot review: Found $UNRESOLVED_THREADS unresolved threads - issues need addressing" | |
| fi | |
| echo "Copilot OK: $COPILOT_OK" | |
| # Check Unity Tests status (commit status, not check run) | |
| UNITY_STATUS=$(gh api repos/${{ github.repository }}/commits/$HEAD_SHA/status --jq '.statuses[] | select(.context == "Unity Tests") | .state' | head -1) | |
| echo "Unity Tests status: $UNITY_STATUS" | |
| # If Unity Tests doesn't exist (skipped scenario), check if Skip Unity Tests completed | |
| if [ -z "$UNITY_STATUS" ]; then | |
| SKIP_STATUS=$(gh api repos/${{ github.repository }}/commits/$HEAD_SHA/check-runs --jq '.check_runs[] | select(.name == "Skip Unity Tests") | .conclusion' | head -1) | |
| echo "Skip Unity Tests status: $SKIP_STATUS" | |
| if [ "$SKIP_STATUS" == "skipped" ] || [ "$SKIP_STATUS" == "success" ]; then | |
| UNITY_STATUS="success" | |
| fi | |
| fi | |
| # Determine if we should approve | |
| if [ "$COPILOT_OK" == "true" ] && [ "$UNITY_STATUS" == "success" ]; then | |
| echo "All required checks passed and Copilot found no issues!" | |
| echo "should_approve=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Required checks not yet passed or Copilot found issues" | |
| echo "should_approve=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check if already approved | |
| id: existing | |
| if: steps.check.outputs.should_approve == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PR_NUMBER="${{ steps.pr.outputs.number }}" | |
| # Check for existing approval from github-actions bot | |
| EXISTING=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews --jq '[.[] | select(.user.login == "github-actions[bot]" and .state == "APPROVED")] | length') | |
| if [ "$EXISTING" -gt 0 ]; then | |
| echo "Already approved by bot" | |
| echo "already_approved=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Not yet approved by bot" | |
| echo "already_approved=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Auto approve PR | |
| if: steps.check.outputs.should_approve == 'true' && steps.existing.outputs.already_approved == 'false' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PR_NUMBER="${{ steps.pr.outputs.number }}" | |
| gh pr review $PR_NUMBER -R ${{ github.repository }} --approve --body "Auto-approved: Copilot review found no issues and Unity Tests passed (or were skipped for non-code changes)." | |
| echo "PR #$PR_NUMBER approved!" |