AIなどの特定ユーザーの作成したPRに要求するApproval数を増やす
Botなどが作成したPull Requestに対して、GitHub Actionsを利用して通常より多くのApproveを要求する方法
現時点で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個以上になっても失敗したままになってしまう。