GitLab MRレビュー自動化を壊さず運用する設計と実装
結論
MRレビュー自動化は、次の3ジョブに分けると安定します。
llm-review: 判定とレポート作成apply-review-fix: approve時だけ修正commitapprove-after-green: bot commit後のgreen確認後にapprove
この設計で、botループを防ぎつつ、低品質記事のマージを確実にブロックできます。
前提
- GitLab CE 17.x
- Runner: shell executor
- Python 3.12
- OpenRouter API (
anthropic/claude-sonnet-4,deepseek/deepseek-chat,google/gemini-2.5-flash) OpenRouter設定例:
export OPENROUTER_API_KEY="your_key_here"
# 環境変数またはGitLab CI/CD Variables で設定
- ブランチ保護:
mainは「成功パイプライン必須」 注意: この設定により、パイプラインが失敗した場合は自動的にマージがブロックされます。GitLabの「Settings > Repository > Push Rules」で設定可能です。
なぜ1ジョブ完結をやめたか
以前は1ジョブで「判定→修正push→approve」を実行しました。 その結果、次の障害が発生しました。
- bot commitで同じレビューが再発火する
- push競合でapproveが先に走る可能性がある
- 失敗時にどこで壊れたか追跡しづらい
実装
.gitlab-ci.yml
stages:
- review
- apply
llm-review:
stage: review
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_MESSAGE =~ /\[bot-review\]/'
when: never
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- content/**/*.md
script:
- python -m review.scripts.review_pipeline
apply-review-fix:
stage: apply
needs: ["llm-review"]
script:
- python -m review.scripts.apply_fix_from_report
判定ロジック
editor_reject: 編集者が明示的に拒否した場合 editor_has_critical_thin_content: 記事が極端に短く、重要な内容が不足している場合
# 例: editor=0.4, reviewer1=0.3, reviewer2=0.3
weighted_score = sum(score * weight)
if all_reject: reject
elif editor_reject: reject
elif editor_has_critical_thin_content: reject
elif weighted_score >= 70 and approve_count >= 2: approve
else: request_changes
push時のループ防止
commit message: chore(review): apply ai suggestions [bot-review]
[bot-review] を rules で除外することで、同一MR内での再帰を止めます。
重要: rules の条件は上から順に評価されるため、除外条件を最初に配置する必要があります。
失敗モードと動作
reject
review/rejected-finalラベル- MRコメントに拒否理由と改善方針
- MRをClose
- pipelineはfailed
request_changes
review/changes-requestedラベル- MRはopen維持
- pipelineはfailed
- 作者の追コミットで再評価
approve
- 修正差分があればsource branchへpush
review/approvedラベル- bot commit後のpipelineがgreenならapprove
実装詳細:
approve-after-greenジョブは別パイプラインで実行され、前回のパイプライン状態をGitLab APIで確認してからapproveを実行します。
system_error
review/system-errorラベル- MRはopen維持
- retryで復旧可能
実測値(2026-02)
対象: 12 MR / 12記事
- 平均レビュー時間: 142秒
- OpenRouter APIエラー率: 3.1%(37/1190、HTTP 429/5xx/timeout)
- フォールバック成功率: 100%(9/9)
- 重複コメント: 0件
- ループ発生: 0件
APIエラー率は HTTP 429, 5xx, timeout の合計を分子にしています。
フォールバック戦略: プライマリAPI失敗時は、設定された順序で代替モデル(deepseek → gemini)に自動切り替えします。全て失敗した場合は system_error 状態になります。
セキュリティ
curl --insecureはデフォルト無効site-bundle.lockをsourceしない(キー抽出に変更)
source は任意シェル実行の攻撃面になるため、CIでは禁止しました。
まとめ
- 判定と副作用を分離する
- ループは commit marker で遮断する
- 失敗モードを
reject / request_changes / system_errorに分離する
この3点を先に固定すると、MRレビュー自動化は壊れにくくなります。