AIなどの特定ユーザーの作成したPRに要求するApproval数を増やす
現時点で Ruleset や Branch protection rules では、ユーザーやヘッドブランチのパターンに応じてルールを適用することはできない。
そのため、AI などの Bot アカウントによって作成された PR に対して、通常よりも厳格なルールを適用したい場合は、GitHub Actions を利用する必要がある。
例:Devin の PR に 2 個以上の Approvals を要求する
PR が変更または PR がレビューされる度に、現在の Approval 数を確認し、指定された数に達していない場合は失敗したステータスを発行し、指定された数に達した場合は成功したステータスを発行することで実現できる。
この例では Devin を対象にしているが、AI_USERNAME を変更することで任意のユーザーを対象にすることができる。
name: Check Approvals for Devin PRs
on: pull_request_review: types: [submitted] pull_request: types: [opened, synchronize, reopened]
env: AI_USERNAME: devin-ai-integration[bot] REQUIRED_APPROVALS: 2
permissions: pull-requests: read statuses: write
jobs: check-ai-prs: runs-on: ubuntu-latest steps: - name: Check AI PRs if: github.event.pull_request.user.login == env.AI_USERNAME uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | // 略:GitHub Scripts部分を参照 - name: Skip Check (Not an AI PR) if: github.event.pull_request.user.login != env.AI_USERNAME uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { owner, repo } = context.repo;
await github.rest.repos.createCommitStatus({ owner, repo, sha: context.payload.pull_request.head.sha, state: 'success', context: `AI Approval Requirement`, description: 'This PR was not created by the AI bot. Skipping approval check.', target_url: `https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id}}` });
GitHub Scripts 部分:
/*レビューの取得 */const { owner, repo } = context.repo;// pull_request_reviewの場合でもpull_requestと同様に取得できるconst pr = context.payload.pull_request;const requiredApprovals = ${{ env.REQUIRED_APPROVALS }};const { data: reviews } = await github.rest.pulls.listReviews({ owner, repo, pull_number: pr.number});
/* 最終的なステータスがApprovedのユーザーを取得 */const approvers = new Set();const latestReviewStates = new Map();for (const review of reviews) { // 後からコメントされたケースでもApprovedとして扱われるためコメントは無視 if (review.state === 'COMMENTED') { continue; } latestReviewStates.set(review.user.login, review.state);}for (const [user, state] of latestReviewStates.entries()) { if (state === 'APPROVED') { approvers.add(user); }}console.log(`Current unique approvers: ${approvers.size} (${Array.from(approvers).join(', ')})`);
/* 状況に合わせてステータスを発行 */const isSuccess = approvers.size >= requiredApprovals;await github.rest.repos.createCommitStatus({ owner, repo, sha: pr.head.sha, state: isSuccess ? 'success' : 'failure', context: `AI Approval Requirement`, description: isSuccess ? 'All required approvals have been received.' : `Waiting for ${requiredApprovals - approvers.size} more approval(s).`, target_url: `https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id}}`});
ルールの設定とステータスを発行する理由
このワークフローによって発行されるステータス(例ではAI Approval Requirement
)を Ruleset や Branch protection rules で必須にすることで、Devin の PR に対しては 2 個以上の Approval がないとマージが出来ないようになる。
ワークフロー自体のステータスではないため注意。
どのトリガーに対しても常に同じステータスを発行してほしい場合は、実行したワークフローのステータスとは別に GitHub Status API を利用してステータスを発行し、そのステータスを扱う必要がある。
なぜなら、GitHub Actions で自動的に生成されるステータスがトリガー毎に生成される上、ルールで指定した場合すべてのトリガーで success することが要求されるようになってしまうためである。具体的に例ではCheck Approvals for Devin PRs (pull_request)
とCheck Approvals for Devin PRs (pull_request_review)
の 2 つを満たす必要が発生するが、pull_request
の方はレビューで発火しないため approvals が 2 個以上になっても失敗したままになってしまう。