SSD Nodes Learn 🎉 VPS $4.99/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-07

VPSでAI PRレビューエージェントを自分でホストする方法

VPS上で動くAI PRレビューエージェントの構築方法を解説します。self-hosted runner、差分限定プロンプト、パスとサイズ制限、インラインコメント、PR単価を確認できます。

自分でホストする PR レビューエージェントの役割

自分で所有するサーバー上で動作する小規模なプログラムです。プルリクエスト(PR)の差分を読み取り、変更された行だけをモデルに送信します。モデルから返された内容は、インラインのレビューコメントとして投稿されます。ブランチを checkout することはありません。プルリクエストで変更されていないファイルを読み取ることもありません。保持する認証情報は、モデル API(application programming interface)キー 1 つと、コメントの投稿だけが可能なトークン 1 つです。

モデルは差分を読み取れます。この部分は解決済みです。重要なのは、差分がどこへ送られ、誰がキーを保持するかです。ホスト型のレビュー bot を使うと、すべての private repository の差分がネットワークの外へ出て、第三者のログに記録され、その保持ポリシーの対象になります。自分で所有する VPS(virtual private server)では、差分は GitHub から自分のサーバーを経由してモデル API へ送られます。また、何を送信するかを決める 40 行のコードを自分で確認できます。

開始前に必要なもの

  • セルフホスト GitHub Actions runner をリポジトリに登録済みの Ubuntu 24.04 対応 VPS。登録時に追加ラベル pr-review を付けてください。以下のワークフローはこのラベルを条件に runner を選択します。
  • Claude Console で取得した Anthropic API key。
  • pull request を作成できるユーザーを管理できるリポジトリ。private repository が最も簡単です。public repository の場合については以下の fork セクションで説明しますが、より厳しい制約があります。

VPS に reviewer をインストールする

runner サービスは、./svc.sh install の実行時に作成した非特権アカウントで実行されます。ジョブが sudo なしで reviewer を実行できるように、同じアカウントに reviewer をインストールしてください。以下の runner をアカウント名に置き換えます。

sudo apt update && sudo apt install -y gh python3-venv
sudo install -d -m 755 -o runner -g runner /opt/pr-review
sudo -u runner python3 -m venv /opt/pr-review/venv
sudo -u runner /opt/pr-review/venv/bin/pip install anthropic
gh --version

2026 年 8 月時点で Ubuntu 24.04 の gh --versiongh version 2.45.0 を出力します。2.20 以降のリリースでは、以下で使用する --input フラグを利用できます。Command 'gh' not found メッセージが表示される場合は universe コンポーネントが有効になっていないため、sudo add-apt-repository universe を実行してから再試行してください。

鍵とトークンの保存場所

2 つの Secret には、それぞれ異なる有効期間があります。どちらもリポジトリに保存しません。

ANTHROPIC_API_KEY はリポジトリ Secret です。Settings、Secrets and variables、Actions の順に移動して設定します。GitHub がこれを暗号化し、実行時に step の環境へ注入します。ディスク上のファイルになることはなく、git の履歴にも残りません。

GITHUB_TOKEN は動作が異なります。Actions は job ごとに新しいトークンを発行し、job の終了時に破棄します。そのトークンで実行できる操作は、workflow の permissions: ブロックで指定します。最小権限を実現するのはここです。

permissions:
  contents: read
  pull-requests: write

このトークンでは review を投稿できます。ただし、commit の push、branch の merge、workflow ファイルの編集、別の repository へのアクセスはできません。コメントを投稿できる agent は reviewer です。push できる agent は committer であり、その権限に合意したわけではありません。model key も同じように慎重に扱ってください。この key はアカウントの費用を消費するためです。この問題への対処については、AI agent の到達範囲から Secret を外すにも詳しく説明しています。

Actions は、job のログ内で完全一致する Secret の文字列を *** に置き換えます。完全一致だけが対象となるため、key を base64 エンコードした場合、2 行に分割した場合、または 1 文字ずつ出力した場合は、そのまま表示されます。環境変数をダンプする debug step は追加しないでください。

フォークからの pull request が API key を取得できない理由

GitHub のルールは単純です。GITHUB_TOKEN を除き、フォークされた repository から workflow がトリガーされた場合、secret は runner に渡されません。そのため、フォークからの pull_request 実行ではスクリプトに ANTHROPIC_API_KEY が設定されず、最初の API 呼び出しが invalid x-api-key で失敗します。

考えられる対処は、トリガーを pull_request_target に変更することです。これなら base repository のコンテキストで実行され、secret も取得できます。ただし、ここではこの方法を使わないでください。GitHub 自身のセキュリティガイダンスでは、この種の workflow は「特権を持つため、他の特権付き workflow trigger と main branch の同じ cache を共有し、repository への write access や参照先の secret への access を持つ可能性がある」と説明されています。また、その結果は「repository の乗っ取りに悪用される可能性がある」とされています。

同じガイダンスでは、runner についても明確に警告しています。「GitHub の public repository では、self-hosted runner をほぼ決して使用してはいけません。任意のユーザーがその repository に対して pull request を作成でき、環境を侵害できるためです。」

このため、設計上の選択が 2 つあります。job に guard を設定し、自分の repository に push された branch でのみ実行します。また、workflow には actions/checkout step を一切含めません。agent が branch をディスク上に保持することはないため、悪意のある pull request は model に送信されるテキストにすぎません。VPS 上で実行されるものがないため、攻撃者が VPS 上で build script を実行することもできません。

差分を取得し、リポジトリ全体は取得しない

1 回のリクエストで、差分全体をプレーンテキストとして取得できます。

export GH_TOKEN=your_token   # in the workflow this comes from secrets.GITHUB_TOKEN
gh api /repos/OWNER/REPO/pulls/42 -H "Accept: application/vnd.github.diff"

Accept: application/vnd.github.diff メディアタイプを指定すると、レスポンスはプルリクエストを説明する JSON オブジェクトではなく、統合差分そのものになります。gh api はその本文を変更せずに出力します。表示される最初の行は diff --git a/ で始まります。gh: Not Found (HTTP 404) は、トークンからリポジトリを参照できないことを示します。きめ細かな個人用トークンでは、Pull requests 権限を有効にしていないことがほとんどの原因です。

トークンを消費する前にフィルタリングする

このセクションが、読まれるボットとミュートされるボットを分けます。以下の各フィルタは、モデルが 1 バイトも見る前に実行されます。

  • パスフィルタ。 ロックファイル、vendor ディレクトリ、minify 済みのバンドル、生成コードを対象外にします。モデルが package-lock.json についてコメントしても、まったく意味がありません。これらのファイルは、diff の大部分を占めることもよくあります。
  • サイズ上限。 上限を超えた場合はレビューをスキップし、正常終了します。4,000 行のリファクタリングに対して、推測を 60 個並べるのではなく、自動レビューには大きすぎるという説明を 1 行だけ返します。
  • 重大度のしきい値とコメント数の上限。 重大度が high または medium の指摘を、最大 10 件まで重大度の高い順に報告します。11 件目のコメントを読む人はいません。

スクリプト

これを /opt/pr-review/review.py として保存します。設定は環境から読み込むため、コードを変更せずにワークフローでモデルを切り替えられます。

#!/usr/bin/env python3
"""Review only the changed lines of one pull request."""
import json
import os
import subprocess
import sys

import anthropic

REPO = os.environ["GITHUB_REPOSITORY"]
PR = os.environ["PR_NUMBER"]
MODEL = os.environ.get("REVIEW_MODEL", "claude-haiku-4-5-20251001")
MAX_DIFF_BYTES = int(os.environ.get("MAX_DIFF_BYTES", "120000"))
MIN_SEVERITY = os.environ.get("MIN_SEVERITY", "medium")
MAX_COMMENTS = 10
RANK = {"low": 0, "medium": 1, "high": 2}
SKIP = ("package-lock.json", "poetry.lock", "/vendor/", "/node_modules/", ".min.js")

raw_diff = subprocess.run(
    ["gh", "api", f"/repos/{REPO}/pulls/{PR}",
     "-H", "Accept: application/vnd.github.diff"],
    check=True, capture_output=True, text=True,
).stdout

ファイルごとに差分を分割することで、パスによるフィルタリングが可能になります。各行に番号を付けることで、レビューコメントを配置できるようになります。GitHub がインラインコメントを受け付けるのは、差分に含まれる行だけです。そのため、モデルは実在する行番号を示す必要があります。番号を渡しておけば、モデルは番号を作り出さずにそのまま使用できます。

def per_file(diff_text):
    """Split a unified diff into one string per file."""
    sections, current = [], []
    for line in diff_text.splitlines():
        if line.startswith("diff --git ") and current:
            sections.append("\n".join(current))
            current = []
        current.append(line)
    if current:
        sections.append("\n".join(current))
    return sections


def annotate(section):
    """Prefix every line that exists in the new file with its line number."""
    out, n, in_hunk = [], 0, False
    for line in section.splitlines():
        if line.startswith("@@"):
            n = int(line.split("+")[1].split(",")[0].split(" ")[0])
            in_hunk = True
            out.append(line)
        elif not in_hunk or line.startswith(("-", "\\")):
            out.append(line)
        else:
            out.append(f"{n}\t{line}")
            n += 1
    return "\n".join(out)


kept = [s for s in per_file(raw_diff)
        if not any(p in s.split("\n", 1)[0] for p in SKIP)]
payload = "\n".join(annotate(s) for s in kept)

if not payload.strip():
    print("every changed file was filtered out")
    raise SystemExit(0)
if len(payload) > MAX_DIFF_BYTES:
    print(f"diff is {len(payload)} bytes, over the {MAX_DIFF_BYTES} cap")
    raise SystemExit(0)

ハンクヘッダーには行番号の情報が含まれています。@@ -12,7 +12,9 @@ は新しいファイルのハンクが 12 行目から始まることを示すため、カウンターもそこから開始し、追加行と変更されていない行だけで進みます。削除行は新しいファイルに存在しないため、番号を付けずに処理します。バックスラッシュで始まる行を除外する条件は、ファイル末尾に改行がないことを git が示すマーカーを読み飛ばします。これを除外しないと、それ以降の番号がすべて 1 つずれます。

どちらの終了処理でも status 0 を使用し、1 は使用しません。フィルタリングされた pull request やサイズが大きすぎる pull request でも、green check を表示する必要があります。人が対処できない red check は無視されます。そして 1 つの check が無視されるようになると、すべての check が無視されます。

SYSTEM = (
    "You review one pull request diff. Every line that exists in the new file is "
    "prefixed with its line number and a tab character. "
    "Report only defects you can see in the lines shown: a crash, a resource leak, "
    "a security mistake, a wrong boundary condition, a broken contract with code "
    "that is visible in this diff. Do not comment on style, naming or formatting. "
    "Do not guess about code you cannot see. Leave out anything you are not "
    "certain about. An empty findings list is a normal and common answer. "
    'Reply with JSON only, in this shape: {"findings": [{"path": "src/app.py", '
    '"line": 42, "severity": "high", "comment": "what is wrong, then why"}]} '
    "Every line number must be one you can see in the left column of that file."
)

client = anthropic.Anthropic()
message = client.messages.create(
    model=MODEL,
    max_tokens=2000,
    system=SYSTEM,
    messages=[{"role": "user", "content": payload}],
)
print(f"stop={message.stop_reason} in={message.usage.input_tokens} "
      f"out={message.usage.output_tokens}", file=sys.stderr)

text = message.content[0].text
findings = json.loads(text[text.find("{"):text.rfind("}") + 1])["findings"]
findings = [f for f in findings if RANK.get(f["severity"], 0) >= RANK[MIN_SEVERITY]]
findings.sort(key=lambda f: -RANK.get(f["severity"], 0))
del findings[MAX_COMMENTS:]

if not findings:
    print("nothing above the severity threshold; posting no comment")
    raise SystemExit(0)

review = {
    "event": "COMMENT",
    "body": f"Automated review of the changed lines. {len(findings)} finding(s).",
    "comments": [
        {"path": f["path"].removeprefix("b/"), "line": f["line"], "side": "RIGHT",
         "body": f"**{f['severity']}** {f['comment']}"}
        for f in findings
    ],
}
subprocess.run(
    ["gh", "api", "-X", "POST", f"/repos/{REPO}/pulls/{PR}/reviews", "--input", "-"],
    input=json.dumps(review), text=True, check=True,
)

このブロックには、処理に不可欠な点が 3 つあります。JSON は最初の { と最後の } の間で切り出します。モデルが回答を code fence で囲むことがあり、json.loads はその fence を処理できないためです。path の先頭にある b/ は削除します。この接頭辞は diff header に由来しますが、GitHub が必要とするのはリポジトリ相対パスです。また、--input - はレビュー全体を 1 回の API 呼び出しで送信します。そのため、10 件の指摘が 10 件の通知ではなく、1 件の通知として届きます。

報告する内容がない場合、スクリプトは何も投稿しません。すべての pull request に対して bot が「問題は見つかりませんでした」と投稿すると、人は内容を読み飛ばすようになります。その結果、本当に重要な投稿まで読み飛ばされます。

ワークフローに組み込む

次の内容を .github/workflows/pr-review.yml として保存します。

name: pr-review

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths-ignore:
      - '**.md'
      - 'docs/**'

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: pr-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  review:
    if: github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.pull_request.labels.*.name, 'no-ai-review')
    runs-on: [self-hosted, linux, pr-review]
    steps:
      - name: Review the changed lines
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REVIEW_MODEL: claude-haiku-4-5-20251001
          MIN_SEVERITY: medium
        run: /opt/pr-review/venv/bin/python /opt/pr-review/review.py

GITHUB_REPOSITORY は、Actions がすべてのジョブに対してすでに設定するため、その env: ブロックには含めません。concurrency グループは料金に関係します。これがないと、ブランチに短時間で 3 つの修正を push した場合に、完全なレビューが 3 回実行され、3 回分の料金が発生します。これがある場合は、最後の修正だけが残ります。

if: の行には 2 つの役割があります。前半では fork からの pull request をスキップします。キーがないため、どのみち失敗するためです。後半では、チームが実行を停止できるようにします。pull request に no-ai-review ラベルを追加すると、ジョブは実行されません。

pull request を開き、動作を確認します。

gh run list --workflow=pr-review.yml --limit 3
gh run view --log
gh pr view 42 --comments

ログに nothing above the severity threshold; posting no comment と表示され、数秒で完了する実行は正常に動作しています。小規模で変更の少ない pull request では、これが想定される結果です。

自動化した pull request レビューのコストはいくらですか?

入力のほとんどは diff なので、料金は diff のサイズで決まります。以下は 500 行の diff 1 件と system prompt を実測した結果です。推定値ではなく、token counting endpoint で数えています。

ChartTokens for one 500 line pull request diff, measured August 2026
The data behind this chart
[
  {
    "label": "Haiku 4.5",
    "input_tokens": "8,000",
    "output_tokens": "1,200"
  },
  {
    "label": "Sonnet 5",
    "input_tokens": "10,400",
    "output_tokens": "1,560"
  },
  {
    "label": "Opus 5",
    "input_tokens": "10,400",
    "output_tokens": "1,560"
  }
]

この diff は、Haiku 4.5 では入力トークン 8,000、Sonnet 5 では 10,400 でした。同じテキストでも、モデルによってカウントが異なります。Claude 4.7 以降のモデルは新しい tokenizer を使用するため、同じ入力でもトークン数が約 30% 増えます。この点は Anthropic の料金ページにも記載されています。100 万トークンあたりの料金だけで新しいモデルと古いモデルを比較する場合は、この差を考慮してください。

2026 年 8 月時点の定価は、Haiku 4.5 が入力 100 万トークンあたり $1、出力 100 万トークンあたり $5 です。Sonnet 5 は、2026 年 8 月 31 日までの導入価格では $2 と $10 で、その後は $3 と $15 になります。Opus 5 は $5 と $25 です。

ChartCost of that one review at list prices, August 2026
The data behind this chart
[
  {
    "label": "Haiku 4.5",
    "cost_per_pr_cents": 1.4,
    "cost_200_prs_usd": "2.80"
  },
  {
    "label": "Sonnet 5",
    "cost_per_pr_cents": 3.64,
    "cost_200_prs_usd": "7.28"
  },
  {
    "label": "Opus 5",
    "cost_per_pr_cents": 9.1,
    "cost_200_prs_usd": "18.20"
  }
]

pull request 1 件あたりの料金は、Haiku 4.5 で 1.4 セント、Opus 5 で 9.1 セントです。チームが月に 200 件の pull request をマージする場合、Haiku 4.5 では約 $2.80、Sonnet 5 では $7.28、Opus 5 では $18.20 かかります。2026 年 9 月 1 日以降は、Sonnet 5 の金額を 1.5 倍してください。

実際の請求額をこの見積もりより増やす要因が 2 つあります。synchronize の trigger は push のたびにレビューを実行するため、8 回 push した active branch では 8 回分のレビュー料金がかかります。concurrency rule が効果を発揮するのは、複数の push が近い時間に発生した場合だけです。また、ここでの数値は path filters が正しく機能することを前提としています。フィルターされていない lock file が 1 つあるだけで、入力トークン数が 2 倍になることがあります。

Prompt caching はこの用途では役に立ちません。キャッシュする prefix は呼び出し間で byte 単位で同一である必要がありますが、diff は毎回変わります。安定しているのは system prompt だけで、cacheable length の最小値を大きく下回ります。一般的なルールについては prompt caching が費用に見合う場合、上記 3 つのモデルから選ぶ方法については 用途ごとに使用する Claude model を参照してください。

有効化する前に自分の diff を測定する

payload を作成した直後に次の行を追加し、先月の pull request をいくつか対象として、script を手動で実行します。

print(client.messages.count_tokens(
    model=MODEL, system=SYSTEM, messages=[{"role": "user", "content": payload}]
).input_tokens)

count endpoint は model を実行しないため、入力トークンも出力トークンも消費しません。また、指定した model に対応する tokenizer を使用します。自分の repository から実際の pull request を 10 件選んで実行し、平均値ではなく中央値を使用してください。非常に大きな migration が 1 件あると、平均値が大きく変わるためです。

レビュー bot が無効化される理由と、その回避方法

この bot への信頼を損なう動作は 2 つあります。どちらも、上のコードで対策しています。

すべてを一度にレビューすること。 40 件のコメントを残す bot は、どのコメントも読まれません。重大度のしきい値とコメント数の上限 10 件は、礼儀のためではありません。本当に重要な指摘を見える状態に保つためのものです。切り捨てる前に重大度で並べ替えると、上限を超えた場合に重要度の低い指摘から除外できます。無作為に 10 件を残すことにはなりません。

確認できない内容について、確信があるようにコメントすること。 これが原因で、エンジニアは bot を完全に無効化します。40,000 行のコードベースのうち 200 行だけを見せたモデルでも、見たことのないファイルについて「これは redis_client.py のキャッシュ無効化を壊します」と書くことがあります。system prompt では、表示された行から確認できる不具合だけを報告し、確信できない内容は省くよう明確に指示しています。一般的に正確さを求めるより、確認できない場合の失敗を直接示すほうが効果的です。また、結果が空でも正常であるとモデルに伝えることで、2 行の変更について無理に何かを作り出すことを防げます。

レビューは COMMENT として投稿し、REQUEST_CHANGES として投稿しないでください。モデルの意見でマージをブロックできる状態にしてはいけません。そうなると、締め切りに追われた人が bot と議論するのではなく、ワークフロー全体を削除してしまいます。

障害パターンと表示される文字列

レビューの投稿時に HTTP 422 が返る場合。 ghgh: Unprocessable Entity (HTTP 422) を出力し、レスポンス本文にはフィールド名が示されます: Pull request review thread line must be part of the diff。GitHub はそのコメントの位置を特定できません。よくある原因は、モデルが生成した行番号、b/ プレフィックスが残った path、または削除された行へのコメントです。削除された行の場合は、sideRIGHT ではなく LEFT に設定する必要があります。投稿前にレビュー JSON を出力し、コメントを1件、手作業で diff と照合してください。

モデル API から invalid x-api-key が返る場合。 最初の messages.create 呼び出しでステップが失敗します。ANTHROPIC_API_KEY Secret がリポジトリに設定されていないか、pull request が fork から送信されているため、Actions に Secret が一切渡されていない可能性があります。if: 行の fork ガードでスキップされるはずなので、まずその行を確認してください。

gh: Resource not accessible by integration (HTTP 403) ジョブトークンに pull request への書き込み権限がありません。permissions: ブロックに pull-requests: write を追加してください。すでに設定されている場合は、Settings、次に Actions、次に General を確認してください。組織のポリシーにより、すべての workflow token が要求できる権限の上限が設定されている場合があります。

json.decoder.JSONDecodeError モデルが解析可能な JSON を返しませんでした。よくある原因は、レスポンスがトークン上限に達し、オブジェクトの途中で終了したことです。ログ行には、この状況を確認するために stop_reason が出力されます。max_tokens の値が示す場合は、max_tokens を増やすか MAX_COMMENTS を減らしてください。

workflow がまったく実行されない場合。 pull request に対して gh run list に何も表示されません。まず paths-ignore が変更されたファイルをすべて除外していないか確認し、次に fork ガードとラベルガードを確認してください。その後、VPS で sudo systemctl status 'actions.runner.*' を実行して runner が稼働しているか確認します。runner がオフラインの場合、pull request のどこにもエラーメッセージが表示されないまま、ジョブがキューに残ります。

すべてのレビューが空のまま返る場合。 1回の実行だけ MIN_SEVERITYlow に設定してください。指摘が表示される場合は、しきい値が機能しています。何も表示されない場合は payload を出力し、フィルターによって diff 全体が除外されていないことを確認してください。

他のエージェントと並行して実行する

レビュアーは小規模なため、他の処理をすべて実行しているサーバーに追加したくなります。リポジトリが重要であれば、分離してください。このプロセスは、コードにコメントできるトークンと、支払いに使用されるキーを保持します。また、self-hosted runner は設計上、ワークフローのコードを実行する場所です。sudo 権限を持たない専用の非特権アカウントを使用し、他の処理を実行していないホストで運用することが最低限必要です。対話型エージェントでもコードを checkout する場合は、エージェントごとに使い捨ての VM を用意する構成が堅実です。一般的な構成については、VPS で coding agent を実行する方法を参照してください。Anthropic API が初めての場合は、まず VPS で Claude API アプリを作成する方が、この構成より小規模に始められます。 専用の非特権アカウント エージェントごとの使い捨て VM VPS で coding agent を実行する VPS で最初の Claude API アプリを作成する

FAQ

AI PR レビューエージェントにはリポジトリへの書き込み権限が必要ですか?

いいえ。レビューを投稿するには pull-requests: write、差分を取得するには contents: read が必要です。必要なのはこの 2 つだけです。これらはワークフローの permissions: ブロックで設定し、ジョブごとの GITHUB_TOKEN で許可できる操作を制限します。この 2 行により、エージェントはプルリクエストにコメントできますが、コミットのプッシュやブランチのマージはできません。マージをブロックできないようにするには、レビューの投稿に REQUEST_CHANGES ではなく event: COMMENT を使用します。

レビューコメントが HTTP 422 で失敗するのはなぜですか?

GitHub がインラインレビューコメントを受け付けるのは、プルリクエストの差分に含まれる行だけです。該当しない行を指定すると、Pull request review thread line must be part of the diff が返されます。path がリポジトリルートからの相対パスになっており、差分ヘッダーの b/ プレフィックスが付いていないことを確認してください。また、その行番号が対象ファイルの hunk 内にあることも確認します。追加または変更されていない行では sideRIGHT にし、削除された行では LEFT にする必要があります。差分をモデルに渡す前に、各行の先頭へ新しいファイルでの行番号を付けておくと、そもそもモデルが行番号を推測せずに済みます。

フォークからのプルリクエストがあるパブリックリポジトリで実行できますか?

この設計では実行できません。GitHub はフォークからトリガーされたワークフローに Secret を渡さないため、モデルのキーがなくなり、実行に失敗します。GitHub は、「パブリックリポジトリでは self-hosted runner をほとんど決して使用すべきではない」とも説明しています。誰でも、あなたのマシン上でコードを実行させるプルリクエストを作成できるためです。パブリックプロジェクトでは、リポジトリ自体にプッシュされたブランチにレビュー対象を制限してください。これは if: ガードが行う制限です。あるいは、レビュー手順を GitHub-hosted runner に移し、差分が自分のインフラストラクチャ外へ出ることを受け入れます。

プルリクエストのレビューにはどのモデルを使うべきですか?

まずは Haiku 4.5 を使用してください。範囲を限定した差分を、固定した欠陥種別の一覧に照らして読む作業は、難しい推論問題ではありません。最も安価なモデルを使えば、月額費用を誰も問題にしない水準に抑えられます。使用する言語やフレームワークで実際のバグを見逃す場合は Sonnet 5 に上げてください。思い込みで変更せず、検出結果を測定して判断します。3 つの中では Opus 5 がプルリクエストごとの費用で大幅に高価です。そのため、すべてのフィーチャーブランチへのすべてのプッシュで使うより、リリースブランチで使うほうが費用を正当化しやすくなります。