anthropics/claude-code-action をラップした composite action と、レビュー観点をまとめた Markdown を 1 リポジトリに集約した、個人用の AI コードレビュー基盤。
レビューは caller 側で用意した GitHub App から投稿され、ユーザーがインラインコメントへ返信すると同じ App が会話で応答する。
Claude はレビューを直接投稿しない。Claude は指摘の構造化 JSON を返すだけで、投稿・絵文字付与・APPROVE 判定・古い APPROVE の dismiss・スレッド resolve は Pavo のスクリプトが決定的に行う。このため Claude に許可されるツールは読み取り系(Read / Grep / Glob / gh pr diff / gh pr view)のみ。
推奨は reusable workflow 経由。target repo に caller workflow を 1 つ追加する。
# .github/workflows/pavo.yml
name: Pavo
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
pull_request_review_comment:
types: [created]
issue_comment:
types: [created]
jobs:
pavo:
# /pavo コマンド以外の通常コメントで runner を起こさないための事前フィルタ
if: ${{ github.event_name != 'issue_comment' || startsWith(github.event.comment.body, '/pavo') }}
uses: k35o/pavo/.github/workflows/review.yml@main
with:
instructions: default,nextjs
secrets: inheritsecrets: inherit で org secrets(K35O_BOT_CLIENT_ID / K35O_BOT_PRIVATE_KEY / CLAUDE_CODE_OAUTH_TOKEN)がそのまま渡る。concurrency・fork スキップ・timeout・App token 発行は reusable workflow 側に集約されているので、caller の改善は Pavo 側の 1 コミットで全リポジトリに届く。
App token の発行やジョブ構成を自分で制御したい場合は、composite action k35o/pavo@main を直接使う(inputs は下の「入力リファレンス」参照)。
@main のままだと最新を追従する。固定したい場合は @<commit-sha> で 40 文字 SHA を指定する(Renovate 等が自動更新できる形)。
GitHub App を作成して、対象リポジトリにインストールする。slug 名は任意。
| 項目 | 設定 |
|---|---|
| Repository permissions | Pull requests: Read & write, Contents: Read(learnings 保存を使うなら Read & write), Issues: Read & write, Metadata: Read |
| Webhook | 「Active」のチェックを外す(不要)。イベントの受け取りは caller workflow の on: が担うため、App の event subscription は使わない |
| secret | 用途 |
|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
Claude Code Max OAuth token (claude setup-token で発行) |
K35O_BOT_CLIENT_ID |
GitHub App の Client ID (Iv23...) |
K35O_BOT_PRIVATE_KEY |
GitHub App の Private key (PEM 全体) |
Pavo 自身は Client ID / Private key を受け取らず、actions/create-github-app-token が発行したインストールトークンと App slug だけを受け取る。
| きっかけ | 動作 |
|---|---|
| PR の open / push / reopen | フルレビュー(2 回目以降は前回レビュー以降の差分に重点) |
| draft → ready for review | フルレビュー(draft のうちはスキップ。review_drafts: true で解除) |
PR コメントで /pavo または /pavo review |
オンデマンド再レビュー。skip label や draft 状態より優先される |
| サイドバーから bot に re-request review | 再レビュー(App が reviewer に指名できる場合のみ。caller の types に review_requested を足す) |
| Pavo のインラインコメントへの返信 | スレッドで会話応答。修正確認できればスレッドを resolve |
/pavoとスレッド返信はauthor_associationがOWNER/MEMBER/COLLABORATORのユーザーのみ受け付ける(public repo で第三者が bot を起動できないようにするため)- Bot が sender のイベント(Renovate 等)はデフォルトでスキップ。
allow_bots: renovateのように許可リストで opt-in できる(自 bot は常に除外) - fork からの PR はスキップ(secrets が渡らないため)
instructions/ 配下の Markdown は 2 種類に分かれる。
- 常時ロード —
system.md(レビューの進め方・信頼境界・severity rubric・verdict 判定)とformatting.md(Markdown の書き方)。スレッド返信ではconversation.mdが persona になる - opt-in 観点 — caller 側で
instructions: default,nextjsのようにカンマ区切りで選ぶ
| 観点 | カバー範囲 | 継承 |
|---|---|---|
default |
バグ・可読性・テスト・ドキュメント・PR description との整合・セキュリティ | — |
frontend |
a11y・フォーム・レイアウトシフト・i18n | — |
react |
useEffect 濫用回避・Concurrent Mode・React 19 |
frontend |
nextjs |
App Router・Server / Client 境界・Server Actions | frontend + react |
typescript |
型の抜け穴・ユニオン網羅性・immutability・型設計 | — |
node |
ESM/CJS・async・stream・child_process・プロセス管理 | typescript |
github-actions |
script injection・permissions・SHA ピン・シェル堅牢性 | — |
walkthrough |
レビュー冒頭に変更サマリテーブル + Mermaid 図を追加出力 | — |
依存関係は instructions/index.json で定義しており、nextjs を指定すると frontend + react も自動ロードされる。未知の観点名や .md の欠落は エラーで落ちる(黙って観点が欠けたままレビューしない)。
./ で始まるエントリは対象リポジトリ内のファイルとして解決される: instructions: default,./docs/review/backend.md のように、fork せずにリポジトリ独自の観点を追加できる。注意: このファイルだけは PR head の内容が使われる(観点ファイルの追加・修正をその PR 自身で有効にするため)。観点ファイルを変更する PR は中身も diff としてレビューされる。
- 重要度は絵文字で表現: 🔴 Critical / 🟡 Warning / 🔵 Suggestion / 👍 Praise
- 各指摘には confidence (0-100) が自己採点され、80 未満は投稿されない(praise を除く)。confidence は検証の到達度(反証を試みたか)に紐づけて採点される
- PR にリンクされた issue(closing references)とコミットメッセージがプロンプトに注入され、「issue の要件を満たしているか」「宣言されていない変更が紛れていないか」の判断材料になる
- 🔴 / 🟡 が 0 件なら
APPROVE(🔵 / 👍 の指摘は APPROVE と同時に inline 投稿される)。approve: falseで常にCOMMENTにできる — branch protection の承認数に Pavo を数えたくない場合はこれを使う pavo:deeplabel 付き PR では、レビュー後に独立した検証セッションが 🔴 / 🟡 の各指摘への反証を試みる。反証された指摘は「その他の観察」に降格され(反証理由付き)、判断できなかった指摘は投稿されない- 新しいレビューの投稿後、同 bot の古い
APPROVEDレビューを dismiss する(投稿「後」なので、実行が失敗しても正当な承認が消えない) - 機械的に適用できる小修正は GitHub の suggestion ブロック(1 クリックでコミット可能)として提案される。投稿前に検証され、現在のコードと同一の no-op suggestion や削除行への suggestion は落とされる
- suggestion で書けない修正には、
claudeCLI にそのまま渡せる Fix prompt が<details>で添付される - 行アンカーが diff に載らない指摘・
min_severity未満の指摘は、レビュー本文の「その他の観察」に折りたたまれる(1 件の不正アンカーでレビュー全体が失われることはない) - 前回レビュー時の SHA をレビュー本文の不可視マーカーに記録し、push 時は前回以降の差分に重点を置く。前回からの interdiff もファイル別に渡される
- 解消が確認できた自分の過去指摘スレッドは自動で resolve される。ただしそのスレッドのファイルが実際に変更された(またはスレッドが outdated になった)場合のみ — コードが変わっていないのに「解消済み」と主張しても resolve されない
- デフォルトブランチの
CLAUDE.md/AGENTS.mdがプロンプトに注入され、プロジェクト規約としてレビュー基準に反映される(PR が規約ファイルを書き換えても、その PR 自身のレビュー基準は変わらない) - 出力言語は PR タイトル・description の主要言語に追従(
language: ja|enで固定可)
caller workflow を触らずにリポジトリ側で調整したい場合、以下のファイルが読まれる。いずれも デフォルトブランチの内容が使われる(PR がレビュー設定を書き換えても、その PR 自身のレビューには効かない。設定変更はデフォルトブランチに merge されてから有効になる)。
-
.github/pavo.json— 設定。action inputs より優先。未知のキーはエラーで落ちる(typo が黙ってデフォルト値に落ちない){ "instructions": "default,typescript", "ignore": ["src/generated/**"], "language": "auto", "approve": false, "min_severity": "warning", "model": "sonnet", "review_drafts": false } -
.github/pavo.md— 自由記述のリポジトリコンテキスト(extra_promptと併用可) -
.github/pavo-learnings.md— レビューのやり取りから蓄積される学習メモ。スレッドで「今後はこうして」と伝えると Pavo が追記し(App にContents: Read & writeが必要)、以後のレビューに反映される。自動追記の保存先はpavo/learningsブランチ(デフォルトブランチは「PR 必須」のルールセットで直コミットできないことが多いため)。読み込みはpavo/learnings→ デフォルトブランチの順で、手書きでデフォルトブランチに置いてもよい
| 入力 | 必須 | デフォルト | 説明 |
|---|---|---|---|
github_token |
✓ | — | レビュー投稿に使うトークン。通常 actions/create-github-app-token の出力 |
app_slug |
✓ | — | GitHub App の slug |
claude_code_oauth_token |
✓ | — | Claude Code Max OAuth token |
instructions |
default |
カンマ区切りの観点名。依存は自動解決。./ は対象 repo 相対 |
|
extra_prompt |
(なし) | リポジトリ固有の追加コンテキスト | |
skip_label |
pavo:skip |
この label が付いていると action をスキップ | |
model |
sonnet |
レビューに使うモデル。PR に pavo:deep label が付くと opus に切り替わり、指摘の反証セッションも追加実行される |
|
language |
auto |
出力言語 (auto / ja / en) |
|
approve |
true |
false で常に COMMENT(APPROVE を出さない) |
|
min_severity |
suggestion |
inline 投稿する最低 severity。未満は本文に折りたたみ | |
ignore_paths |
(なし) | レビュー対象外 glob の追加(lockfile 等の既定除外に加算) | |
allow_bots |
(なし) | PR をレビューする bot login の許可リスト(カンマ区切り) | |
review_drafts |
false |
draft PR もレビューする |
reusable workflow (review.yml) も同名の inputs を持つ。
- PR 単位:
pavo:skiplabel(skip_labelで変更可)。/pavoコマンドは label より優先される - 全体(kill switch): org / repo の Actions variable
PAVO_DISABLEDをtrueにすると、reusable workflow 経由の全実行が gate で即スキップされる - 劣化した変更が
@mainに入ってしまったときのロールバック手順:PAVO_DISABLED=trueで全停止- main を revert する。reusable workflow (
review.yml@main) 経由の caller は action 本体が常に main 追従なので、SHA pin で戻せるのは composite actionk35o/pavo@<sha>を直接使っている caller だけ - 修正を merge してから variable を戻す
- タグやリリース運用は持たず、ブランチか commit SHA で参照する。SHA pin(+ Renovate 追従)が効くのは composite action を直接
uses: k35o/pavo@<sha>する場合。reusable workflow 経由は常に@main追従になる ci.ymlが型チェック(tsc --noEmit)と、index.json の整合・全観点組み合わせのプロンプト生成・gate / 投稿ロジックの単体テスト(node --test)を PR ごとに検証するscripts/*.tsは TypeScript のまま Node の type stripping(Node 22.18+ で標準有効)で直接実行される。ビルドステップ・ランタイム依存はない(typescriptは型チェック用の devDependency のみ)credential-check.ymlが月次で App 鍵と OAuth token の疎通を確認し、失敗すると issue を立てる(トークン期限切れによる全リポジトリ同時停止を事前に検知)metrics.ymlが週次で指摘スレッドの resolve 率・👍/👎 を集計する。各指摘コメントに埋め込まれた不可視マーカー(severity / confidence)を使って、severity 別・confidence 帯別の較正テーブルも出す — confidence 閾値や rubric の調整はこの数字を根拠に行う。任意のリポジトリに対してはREPO=owner/name BOT_NAME='xxx[bot]' node scripts/report-metrics.tsで手動実行できる- ラップしている
claude-code-actionは SHA pin。Renovate で追従する(upstream はインジェクションサニタイザ等の防御を頻繁に更新するため、放置しない)
- 信頼境界: PR description・コード・コメントはすべて「データ」としてフェンス付きでプロンプトに渡され、system.md がそれらの中の指示に従うことを禁止している。レビュー挙動を操作しようとする文章は 🔴 として報告される
- 最小ツール: Claude に許可されるのは読み取り系ツールのみ。GitHub への書き込み(投稿・dismiss・resolve)はすべて Pavo のスクリプトが固定のエンドポイントに対して行う
- 設定ファイルの遮断:
--setting-sources userにより、checkout した PR 内の.claude/settings.json(hooks = 任意コマンド実行)は読み込まれない。レビュー設定(.github/pavo.json等)はデフォルトブランチから読まれるため、PR が自分の審査基準を書き換えることもできない - checkout: PR の head commit を
persist-credentials: falseで checkout する(gh pr diffが示すもの・Readが読むもの・inline コメントのアンカーが常に同一 commit になる)。claude-code-action自身は App token を.git/configに書き込むため、Claude のRead/Grepから.git/配下を deny している - APPROVE の扱い: Pavo の APPROVE は branch protection の承認カウントに入る。必須承認数を Pavo で満たしうる構成にしたくない場合は
approve: falseを設定する
gate.ts— イベント判定(draft / label / bot / association / kill switch)と設定解決(.github/pavo.json> inputs > デフォルト)。全ロジックはユニットテスト済み- プリフライト — bot user の存在・App のインストール・PR 読み取り権限を Claude 実行前に検証(失敗は具体的なエラーで即 fail)
- PR head を checkout
collect-context.ts— GraphQL で全スレッド(resolve 状態・人間の返信込み)・レビュー履歴・コメントを収集し、前回レビュー SHA をマーカーから復元、compare API で差分ファイルを特定build-review-prompt.ts— system + formatting + 観点 + repo 設定 + learnings + 会話コンテキスト + 出力要件を結合claude-code-action— 読み取り専用ツールで diff とコードを調査し、--json-schemaで検証された構造化 JSON(summary / verdict / comments / resolved_comment_ids)を返す- (
pavo:deepのみ)build-verify-prompt.ts+ 2 回目のclaude-code-action— 独立セッションが 🔴 / 🟡 の各指摘への反証を試み、verdict(confirmed / refuted / uncertain)を返す post-review.ts— confidence・ignore・min_severity・アンカー検証・検証 verdict・suggestion 検証でフィルタし、Review を 1 回だけ POST(422 時は本文へ退避して再送)。成功後に古い APPROVE を dismiss、ファイルが実際に変更されたスレッドのみ resolve、$GITHUB_STEP_SUMMARYにメトリクスを出力- 失敗時は
diagnose-claude-failure.tsが claude-execution-output.json から実エラー本文を::error::/ step summary に抽出し、PR に「実行が失敗した」コメントと run URL を残す(サイレント失敗しない)
gate.ts— 返信であること・association を確認- PR head を checkout
build-conversation-prompt.ts— thread root が自 bot か確認し、スレッド全文 + 対象 diff_hunk + repo コンテキストからプロンプトを構築claude-code-action— 構造化 JSON(body / resolve_thread / remember)を返すpost-reply.ts— 返信を POST(JSON は stdin 渡しで shell を経由しない)。修正確認済みならスレッドを resolve、rememberがあれば learnings に追記
.github/workflows/pavo.yml は @main の Pavo で自 PR をレビューする。uses: ./ にしない理由: action.yml や instructions を書き換える PR が「書き換え後の自分」に審査されると、レビューを骨抜きにする変更をその変更自身が承認できてしまう。PR 版のコードは ci.yml が検証する。
| 症状 | 原因と対処 |
|---|---|
GitHub App user xxx[bot] not found |
app_slug の綴り違い。create-github-app-token の app-slug 出力を使う |
App token cannot read <repo> |
App が対象リポジトリにインストールされていない |
App token cannot read PR |
App の Pull requests: Read & write 権限が欠けている |
| レビューが来ない(run は緑) | gate のスキップ理由が Actions ログの ::notice:: に出ている(draft / label / bot / association) |
| レビューが来ない(run が赤) | PR に失敗通知コメントが付く。run URL のログを確認 |
--json-schema was provided but Claude did not return structured_output |
Claude 実行自体の失敗。直後の失敗診断ステップの ::error:: / step summary に result の実エラー本文が出る。cost $0 かつ 1 turn なら CLAUDE_CODE_OAUTH_TOKEN の失効・無効を疑う |
Unknown instruction: xxx |
instructions の typo。既知の観点名はエラーメッセージに列挙される |
| learnings が保存されない | App の Contents 権限が Read のみ。Read & write に変更する |
| 全リポジトリで一斉に止めたい | Actions variable PAVO_DISABLED=true(reusable workflow 経由の場合) |