【完全無料】GitHub Actions × Gemini APIでPR自動コードレビューを自作する【個人開発・SaaS代節約】
はじめに
個人開発や少人数チームの開発において、「コードレビューをしてくれる相手がいない」 というのは共通の悩みです。
初歩的な構文ミスやロジックバグはもちろん、APIキーの誤コミットやSQLインジェクション脆弱性 などのセキュリティ事故は、マージ前に防ぎたいところです。
CodeRabbitやGitHub Copilotなどの商用レビューツールは優秀ですが、月額15〜40ドル/人ほどのコストがかかります。「個人開発でそこまでの固定費は払えない…」という方に向けて、GitHub ActionsとGoogle Gemini API(無料枠)を組み合わせ、完全0円で自律稼働するPR自動コードレビュー環境 を構築しました。
実際に導入・運用してみて遭遇した 「初心者が引っかかりやすいエラーと対処法」 もまとめていますので、ぜひ参考にしてください。
サンプルリポジトリ
実際に動作確認を行ったコード一式および、AIがレビューコメントを投稿したPRの実行例は以下で公開しています。
- リポジトリ: https://github.com/paperlog/ai-review-test
- 実際のPRレビュー例: https://github.com/paperlog/ai-review-test/pull/1
全体アーキテクチャ
仕組みは非常にシンプルで、外部サーバーを立てる必要はありません(完全サーバーレス)。
[開発者] --- PR作成/更新 ---> [GitHub リポジトリ]
│ (ワークフロー自動起動)
▼
[GitHub Actions]
│
├─ 1. git diff で変更差分を取得
├─ 2. 不要ファイル(lock, 画像等)を除外
├─ 3. 差分をGemini APIへ送信
│
▼
[Google Gemini API] (flash-lite / flash)
│ (セキュリティ・バグ・設計の解析)
▼
[GitHub REST API]
│
▼
PRへレビューコメント自動投稿!
前提条件・準備
- Googleアカウント(Gemini APIキー取得用)
- GitHubリポジトリ(動作確認用に新規のテストリポジトリを作成するのがおすすめ)
実装ステップ
1. Gemini APIキーの取得
- Google AI Studio にアクセスし、Googleアカウントでログインします。
- 左メニューの 「Get API key」 > 「Create API key」 をクリックします。
- プロジェクトを選択(または新規作成)し、発行されたAPIキー(
AIzaSy...)を控えておきます。
2. GitHubリポジトリ側の設定
リポジトリの Settings から2箇所の設定を行います。
① APIキーの登録(Secrets)
- 対象リポジトリの Settings > Secrets and variables > Actions を開きます。
- New repository secret をクリックし、以下を登録します。
-
Name:
GEMINI_API_KEY(完全一致・大文字) - Secret: 取得したAPIキー文字列
② PRへの書き込み権限を許可(★重要)
- 同じく Settings > 左メニュー Actions > General を開きます。
- ページ下部の Workflow permissions で 「Read and write permissions」 にチェックを入れて Save します。
- ※デフォルトの読み取り専用のままだと、PRコメント投稿時に
403 Forbiddenエラーになります。
3. ファイルの配置
リポジトリ直下に以下の2つのファイルを作成します。
.
└── .github/
├── workflows/
│ └── ai-code-review.yml
└── scripts/
└── review_code.py
① .github/workflows/ai-code-review.yml
PRの作成・更新を検知してPythonスクリプトを実行する定義です。
name: "AI Automated Code Review"
on:
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
- '**/*.md'
- '**/*.txt'
- '**/*.png'
- '**/*.jpg'
- '**/*.svg'
- '**/package-lock.json'
- '**/yarn.lock'
- '**/pnpm-lock.yaml'
- '**/poetry.lock'
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- name: Checkout target repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python Environment
uses: actions/setup-python@v5
with:
python-version: '3.11'
# 注意: requirements.txt がない状態で cache: 'pip' を指定するとエラーになるため外しています
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install google-genai requests
- name: Run AI Code Review Script
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPOSITORY: ${{ github.repository }}
BASE_REF: ${{ github.event.pull_request.base.sha }}
HEAD_REF: ${{ github.event.pull_request.head.sha }}
run: |
python .github/scripts/review_code.py
② .github/scripts/review_code.py
Git差分を抽出し、フィルタリングした上でGeminiに構造化レビューを生成させ、GitHub PRにコメントするスクリプトです。
import os
import subprocess
import sys
from typing import List
import requests
from google import genai
from google.genai import types
# ------------------------------------------------------------------------------
# 環境変数の検証
# ------------------------------------------------------------------------------
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
PR_NUMBER = os.getenv("PR_NUMBER")
REPOSITORY = os.getenv("REPOSITORY")
BASE_REF = os.getenv("BASE_REF")
HEAD_REF = os.getenv("HEAD_REF")
if not all([GEMINI_API_KEY, GITHUB_TOKEN, PR_NUMBER, REPOSITORY, BASE_REF, HEAD_REF]):
print("[Error] 必須の環境変数が設定されていません。GitHub Secretsを確認してください。", file=sys.stderr)
sys.exit(1)
IGNORED_PATTERNS = [
".lock", "-lock.json", ".min.js", ".min.css", ".map",
".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico",
".pdf", ".csv", ".tsv", ".json", ".xml"
]
MAX_DIFF_CHAR_LIMIT = 25000 # トークンあふれ・費用防止のための文字数制限
def get_pr_diff() -> str:
"""BaseブランチとHeadブランチ間の差分(unified diff)を抽出"""
cmd = ["git", "diff", f"{BASE_REF}...{HEAD_REF}"]
try:
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True,
encoding="utf-8",
errors="replace"
)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"[Error] git diff の実行に失敗しました: {e.stderr}", file=sys.stderr)
sys.exit(1)
def filter_diff_content(raw_diff: str) -> str:
"""不要なバイナリや自動生成ファイルの差分を除外・トリミング"""
filtered_chunks: List[str] = []
current_chunk: List[str] = []
skip_current_file = False
for line in raw_diff.splitlines(keepends=True):
if line.startswith("diff --git"):
if current_chunk and not skip_current_file:
filtered_chunks.append("".join(current_chunk))
current_chunk = [line]
skip_current_file = any(pattern in line.lower() for pattern in IGNORED_PATTERNS)
else:
if not skip_current_file:
current_chunk.append(line)
if current_chunk and not skip_current_file:
filtered_chunks.append("".join(current_chunk))
combined_diff = "".join(filtered_chunks)
if len(combined_diff) > MAX_DIFF_CHAR_LIMIT:
print(f"[Warning] 差分サイズが上限を超えたため切り詰めます。")
combined_diff = combined_diff[:MAX_DIFF_CHAR_LIMIT] + "\n\n...[一部の差分はサイズ上限のため省略されました]..."
return combined_diff
def generate_review_content(diff_text: str) -> str:
"""Gemini APIを呼び出してコードレビューを生成"""
client = genai.Client(api_key=GEMINI_API_KEY)
system_instruction = """
あなたは最高水準のセキュリティ感覚と可読性基準を持つ、シニアソフトウェアエンジニア兼テックリードです。
提示されたGit差分(Pull Requestのdiff)を検証し、以下の【厳格なレビュー基準】に従って日本語で構造化レビューを作成してください。
【レビュー基準】
1. セキュリティリスクの最優先チェック(APIキーやPWのハードコード、SQLインジェクション、XSS等)
2. 堅牢性とバグの検出(境界値チェック、null参照、リソース解放漏れ等)
3. コード設計と可読性(命名の曖昧さ、不要な複雑性等)
【出力フォーマット】
挨拶などの前置きや結びは一切不要です。以下のMarkdown構造のみを出力してください。
## 🔍 AIコードレビュー サマリー
- **判定**: [✅ LGTM (マージ推奨) / ⚠️ 要改善 (修正推奨) / 🚨 警告 (マージ阻止)]
- **変更の概要**: (2〜3行で簡潔に要約)
## 🛠️ 詳細フィードバック
指摘事項ごとに記述(問題がない場合は「重大な問題は検出されませんでした」と記載):
- **対象ファイル / 該当行**: `ファイルパス` (行番号または関数名)
- **重大度**: [🚨 Critical / ⚠️ Warning / 💡 Suggestion]
- **問題の指摘**: 具体的なリスクや欠陥
- **改善案・修正コード**: 修正コード例(コードブロックで明示)
## 🧪 推奨テストケース
- この変更に対して追加検証すべきテスト観点(1〜3点)
"""
user_prompt = f"以下のPull Requestの変更差分をレビューしてください。\n```diff\n{diff_text}\n```"
try:
# 無料枠の利用制限(RPD)を考慮し、軽量・高速な flash-lite 系を推奨
response = client.models.generate_content(
model="gemini-3.5-flash-lite", # 必要に応じて gemini-3.5-flash 等に変更
contents=user_prompt,
config=types.GenerateContentConfig(
system_instruction=system_instruction,
temperature=0.2,
),
)
return response.text.strip()
except Exception as e:
print(f"[Error] Gemini API呼び出しに失敗しました: {e}", file=sys.stderr)
return f"🚨 **AIコードレビュー実行エラー**: Gemini APIの推論中にエラーが発生しました。\n```\n{str(e)}\n```"
def post_github_comment(review_markdown: str):
"""GitHub REST APIでPRにコメント投稿"""
url = f"https://api.github.com/repos/{REPOSITORY}/issues/{PR_NUMBER}/comments"
headers = {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
payload = {"body": review_markdown}
try:
response = requests.post(url, headers=headers, json=payload, timeout=30)
response.raise_for_status()
print(f"[Success] PR #{PR_NUMBER} へのレビューコメント投稿が完了しました。")
except requests.exceptions.RequestException as e:
print(f"[Error] GitHubコメント投稿に失敗しました: {e}", file=sys.stderr)
sys.exit(1)
def main():
print(f"=== PR #{PR_NUMBER} AI自動コードレビュー処理開始 ===")
raw_diff = get_pr_diff()
if not raw_diff.strip():
print("[Info] 差分が存在しません。")
return
filtered_diff = filter_diff_content(raw_diff)
if not filtered_diff.strip():
print("[Info] 対象となるソースコードの変更がありませんでした。")
return
review_result = generate_review_content(filtered_diff)
post_github_comment(review_result)
print("=== 全処理が正常に完了しました ===")
if __name__ == "__main__":
main()
動作確認(テストPRの作成)
実際に脆弱性のあるコードを含んだブランチを作成し、PRを出してみます。
# 1. テストブランチ作成
git checkout -b feature/test-ai-review
# 2. わざと脆弱性のあるコードを作成 (test_sample.py)
cat << 'EOF' > test_sample.py
def fetch_user_data(user_id):
# ダミーAPIキーのハードコード
api_key = "AIzaSyFakeKeyTest123456789"
# SQLインジェクション脆弱性
query = f"SELECT * FROM users WHERE id = {user_id}"
return query
EOF
# 3. コミット&プッシュ
git add test_sample.py
git commit -m "feat: add test code"
git push -u origin feature/test-ai-review
GitHub上でPRを作成すると、約20〜30秒後にGitHub Actionsが自動実行され、PRコメントに以下のようなレビューが届きます!
🔍 AIコードレビュー サマリー
- 判定: 🚨 警告 (マージ阻止)
- 変更の概要: 新規追加されたテストファイルにハードコードされたAPIキーとSQLインジェクションの脆弱性が含まれています。
🛠️ 詳細フィードバック
- 対象ファイル / 該当行:
test_sample.py(3-8行目)- 重大度: 🚨 Critical
- 問題の指摘: テスト用コードであっても、ハードコードされたAPIキーの混入は流出リスクがあります。また、文字列フォーマットによるSQL構築はSQLインジェクションを引き起こします。
- 改善案・修正コード: (パラメータ化クエリや環境変数取得のコード例が提示される)
実際に遭遇した「ハマりどころ」と解決策
実装時に引っかかりやすいポイントをまとめました。
1. PATでプッシュ時に workflow scope エラーが出る
.github/workflows/ 配下のファイルをローカルからプッシュしようとすると、以下のエラーで弾かれることがあります。
! [remote rejected] main -> main (refusing to allow a Personal Access Token to create or update workflow ... without `workflow` scope)
-
原因: GitHubのPersonal Access Token(PAT)に
workflow権限が付与されていないためです。 -
解決策: GitHubの Settings > Developer settings > Personal access tokens を開き、トークン編集画面で
workflowにチェックを入れて保存してください。
2. Actionsが「No file matched to requirements」で即座に失敗する
ワークフロー実行時、Pythonセットアップのステップで落ちるケースです。
No file in ... matched to [**/requirements*]
Process completed with exit code 1.
-
原因:
actions/setup-pythonにcache: 'pip'を指定しているのに、リポジトリにrequirements.txtが存在しないためです。 -
解決策: 今回のように直接
pip installでライブラリを入れる場合は、YAMLファイルからcache: 'pip'の行を削除します。
3. スクリプトが「必須の環境変数が設定されていません」で落ちる
- 原因: リポジトリのSecretsに登録したキー名と、スクリプト側で読み込んでいる名前が不一致(スペルミスや登録漏れ)になっています。
-
解決策: リポジトリの Settings > Secrets and variables > Actions で、シークレット名が厳密に
GEMINI_API_KEYとなっているか確認してください。
4. コメント投稿時に 403 Forbidden エラーになる
-
原因: Actionsのデフォルトトークン(
GITHUB_TOKEN)にPRへの書き込み権限がありません。 - 解決策: リポジトリの Settings > Actions > General > Workflow permissions を 「Read and write permissions」 に変更してください。
5. Gemini APIのモデル選択と無料枠制限(20回/日の壁)
Geminiの通常Flashモデル(gemini-3.5-flash など)を無料枠で利用する場合、1日のリクエスト上限(RPD)が少なめに設定されている ことがあります。
-
対策: 個人開発のテストや小規模運用であれば、
gemini-3.5-flash-liteを指定するのがおすすめです。軽量モデルでありながら差分レビューの精度は十分で、より緩い制限枠で安定して運用できます。
補足:レビュー投稿時にメールが届くのはなぜ?
PRにAIレビューが投稿された際、GitHubから通知メールが届きます。
これはPythonスクリプトでメール送信しているわけではなく、GitHubの標準通知機能 によるものです。「自分のPRに新しいコメント(Botからのコメント)がついた」ため、GitHubが自動配信しています。通知が不要な場合は、PR画面右側の Notifications > Unsubscribe で個別解除できます。
まとめ
- 外部サーバー不要・ホスティング代0円
- GitHub Actionsの無料枠(月2,000分)とGemini APIの無料枠で完全無料運用が可能
- PRを出すだけで、APIキー露出や初歩的バグを即座にブロック
わずか20分程度の設定で、個人開発環境に「24時間稼働してくれる専属テックリード」を導入できます。ぜひ試してみてください!