自分が書いたコードを自分で見て「問題なし」と判断するのは、検証ではないと思っている。だから実装と検証は別の役に分けてきた。Claude Code v2.1.215 で、その運用の前提が1つ外れた。
目次
変わったのは起動する主体だけ
2026年7月19日の changelog に、1行だけ書かれている。
Claude no longer runs the
/verifyand/code-reviewskills on its own; invoke them with/verifyor/code-reviewwhen you want them
スキルが廃止されたわけではない。/verify も /code-review も従来どおり使える。Claude が自分の判断で起動しなくなった、という変更だ。理由の説明は changelog にもドキュメントにも見当たらなかった。
時系列を並べると流れが見える。
| 版 | 変更 |
|---|---|
| v2.1.186 | /review <pr> が /code-review medium と同じレビューエンジンを使うように |
| v2.1.196 | /code-review のクリーンアップ検出を5つから1つに統合し、トークン使用量を約25%削減 |
| v2.1.202 | /review <pr> を高速な単一パスに戻し、多段レビューを /code-review <level> 側へ分離 |
| v2.1.215 今回 | 自動起動を停止 |
レビューは重くて高価だから自動では走らせない方向に、調整が続いているように見える。ここは筆者の読みで、公式の説明ではない。
この2つが何をするスキルなのか
/code-review は現在の diff を対象に、バグと、再利用・簡素化・効率化の観点で指摘を返す。既定のスコープは「upstream より先に進んでいるブランチのコミット+作業ツリーの未コミット変更」。
引数の形は [low|medium|high|xhigh|max|ultra] [--fix] [--comment] [target]。effort について公式はこう書いている。低い水準ほど件数は少なく確度の高い指摘が返り、high から max は網羅性が上がる代わりに不確かな指摘も混じる。引数を省くとセッションの effort が使われる。
--fix で作業ツリーへ適用、--comment で GitHub の PR にインラインコメントを投稿できる。target にはファイルパス、PR 番号、ブランチ名、main...my-feature 形式の ref range が渡せる。
細かいが効く仕様として、ローカル実行は CLAUDE.md を読むが REVIEW.md は読まない。レビュー観点をプロジェクトに持たせたいなら CLAUDE.md 側に書く必要がある。
もう一方の /verify の定義が、筆者には刺さった。公式ドキュメントの表現はこうだ。
Build and run your app to confirm a code change does what it should, without falling back to tests or type checks
テストや型チェックに逃げずに、実際にビルドして起動して確かめる。プロジェクトの種類(CLI、サーバー、TUI、ブラウザ駆動)と README や package.json、Makefile の内容から起動方法を推測するので、設定なしで動く。
データベースや環境変数が要る非標準の構成なら、/run-skill-generator で起動手順を .claude/skills/run-<name>/ に記録しておくと、以後 /run と /verify がその手順に従う。
起動確認だけで納品して、罠を踏ませた話
PaddleOCR を使ったデスクトップアプリを作って配布したことがある。PyInstaller で凍結して .exe にする構成で、手元では問題なく動いていた。起動もする。画面も出る。そこで納品した。
実際には paddlex のメタデータ検出が凍結環境で壊れていて、OCR を実行した瞬間に NameError で落ちた。起動を確認しただけで、肝心の機能を一度も走らせていなかった。
この失敗が厄介だったのは、手元の開発環境では最後まで正常に動いていたことだ。凍結して初めて壊れる種類の不具合で、テストも型チェックも素通りする。「ビルドが通った」も「起動した」も、動くことの証明にはならない。
あれ以来、配布物は実機能の実行まで確認してから完成と言うようにしている。/verify の「テストや型チェックに逃げない」という定義は、この失敗と同じ場所を指している。自動で走らなくなったことを、軽い変更として流したくなかった。
フックで差し戻す
Claude が自分から起動しないなら、こちらで起動させる仕組みを置く。ここで先に知っておきたい制約がある。フックはスラッシュコマンドを直接実行できない。できるのは「終了を止めて、理由を Claude に伝えて差し戻す」ことだ。
Stop フックは Claude が応答を終えたときに発火し、そのターンで試みたツール呼び出しの配列を tool_calls として受け取れる。各要素に tool_name と tool_input が入るので、ファイル編集系が含まれていたら差し戻す、という判定が書ける。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/require-review.sh",
"timeout": 30
}
]
}
]
}
}
#!/bin/bash
input=$(cat)
# このターンでファイルを編集したか
edited=$(jq -r '[.tool_calls[]? | select(.tool_name | test("^(Edit|Write|NotebookEdit)$"))] | length' <<<"$input")
[ "$edited" -eq 0 ] && exit 0
# 差し戻しループ防止: セッションごとに1回だけ
sid=$(jq -r '.session_id' <<<"$input")
marker="/tmp/claude-review-asked-$sid"
[ -f "$marker" ] && exit 0
touch "$marker"
jq -n '{
decision: "block",
reason: "このターンでコードを変更しました。終了する前に /code-review を現在の diff に対して実行し、指摘を報告してください。実アプリでの動作確認が必要な変更なら /verify も実行してください。"
}'
判定を jq で決めているのは、挙動を安定させるためだ。type: "prompt" のフックでも同じことは書ける。既定では Haiku が判定し、{"ok": false, "reason": "..."} を返すと Stop では reason が Claude にフィードバックされる。ただ「編集したかどうか」は機械的に判定できる条件なので、ここに推論を挟む理由がない。
ループガードを自前で用意している点にも触れておきたい。stop_hook_active というフィールドは現行ドキュメントでは permission recovery 中かどうかを示すものと定義されていて、ループ防止用とは説明されていない。マーカーファイルを自分で置くのが確実だった。
複数の Stop フックを登録している場合の挙動も押さえておきたい。公式は「すべてのフックのコマンドが完了してから結果をマージする」と書いている。既にコンテキスト使用率を警告する Stop フックを入れているなら、判定が競合しない設計にする必要がある。
フックを万能扱いしない
ここは正直に書いておきたい。上の設定を筆者はまだ実測していない。公式仕様に沿って組み立てたもので、動作は未検証だ。
加えて、仕様上の限界が2つある。
decision: "block" の reason について、ドキュメントは「Claude は reason を読んで、それに応答できる」と書いている。スキルの起動を保証する機構ではない。差し戻せば必ずレビューが走る、とは書かれていない。
もう1つ。公式はフックの if フィルタについて「best-effort なので、確実な許可・拒否を強制したいなら権限システムを使え」と述べている。フックを絶対の強制装置として設計するのは、公式の姿勢とずれる。
type: "agent" のフックを使えば、フック自身に git diff HEAD を読ませてレビューさせることもできる。最大50ツールターンまで動き、ファイル読みとコマンド実行ができる。ただし公式が「Agent hooks は experimental。本番のワークフローには command フックを推奨」と明記しているので、常用は避けたほうがいい。
3層で受ける
1つの仕掛けで完全に防ぐ発想をやめて、層で受けるほうが現実的だと考えている。
| 層 | 手段 | 強制力 |
|---|---|---|
| 1 | Stop フックでターン終了時に差し戻す |
中(起動保証なし) |
| 2 | 実装役をサブエージェント化し frontmatter にフックを埋める 推奨 | 高 |
| 3 | @agent-code-reviewer の明示呼び出しを習慣にする |
確実(手動) |
2層目が効くのは、サブエージェントの Stop フックが自動的に SubagentStop に変換されるからだ。実装役が仕事を終えるたびに検証が挟まる形になり、「実装と検証を別の役に分ける」という考え方に最も素直に対応する。
3層目については、公式が委譲の確実性を3段階で説明している。
- 自然言語でサブエージェント名を出す — Claude が委譲するかを決めるので保証はない
- @メンション — そのタスクについて実行が保証される。手で打つなら
@agent-code-reviewer - セッション全体 —
--agentフラグかagent設定で、システムプロンプトとツール制限ごと置き換わる
ここは差が大きい。名前を出しただけでは走らないことがある、というのは運用を組むうえで前提にしておきたい。
なお code-reviewer はドキュメントに例として繰り返し出てくる名前で、組み込みで用意されているとは書かれていない。.claude/agents/code-reviewer.md を自分で置く前提だ。
紛らわしいものを2つ
/code-review ultra はクラウド側で深いレビューを走らせるモードで、claude.ai アカウントでの認証が必要になる。Bedrock、Google Cloud Agent Platform、Microsoft Foundry、ゼロデータ保持の組織では使えず、その場合はローカルのレビューにフォールバックする。
もう1つ、GitHub 連携の Code Review はローカルの /code-review とは別物だ。Team と Enterprise 限定の research preview で、1レビューあたり平均 $15 から $25 とされている。名前が似ているので、社内で話すときは区別したほうがいい。
2層目を実際に書くとどうなるか
サブエージェント側にフックを埋める形が、この構成の中心になる。サブエージェントの定義ファイルは frontmatter を持っていて、そこに hooks を書ける。
公式の説明によれば、サブエージェントに定義した Stop フックは自動的に SubagentStop に変換される。サブエージェントが完了したときに発火するイベントがそちらだからだ。
実装役のサブエージェントを1つ用意して、そこに検証を挟む形にすると、役割の分離が構造として残る。実装役が「終わった」と言うたびに検証が走り、通らなければ差し戻される。人間が覚えておく必要がなくなる。
この形の良いところは、フックの適用範囲がそのサブエージェントの寿命に限定されることだ。グローバルな Stop フックだと、調べ物をしただけのターンでも判定が走る。実装役に限定すれば、条件分岐を書かずに済む。
筆者はまだこの構成を組んでいない。フックの動作を実測してから設計するつもりでいる。
レビューの深さをどう選ぶか
手動で呼ぶことになった以上、毎回どの水準で呼ぶかを決める必要が出てくる。
| 水準 | 公式の説明 | 使いどころ |
|---|---|---|
low |
件数は少なく、確度の高い指摘 | 小さな修正、時間がないとき |
medium 既定寄り |
引数を省くとセッションの effort を使う | 通常の実装 |
high 〜 max |
網羅性が上がる代わりに不確かな指摘も混じる | 公開前、境界をまたぐ変更 |
不確かな指摘が混じるという点は、そのまま受け取っていい。網羅性を上げれば偽陽性も増える。指摘を全部直そうとすると時間が溶けるので、high 以上で回したときは「これは実際に壊れるか」を自分で判断する前提で見る。
スコープの指定も効く。main...my-feature のような ref range を渡せば、そのブランチで入れた変更だけを見てもらえる。作業ツリーが散らかっているときは、対象を絞ったほうが指摘が読みやすい。
ついでに見つけた不整合
調べていて気づいたことを1つ。/code-review と /security-review はコマンドリファレンスの一覧表に載っているのに、/verify、/run、/run-skill-generator は載っていない。skills ページの「Run and verify your app」セクションにだけ書かれている。
コマンド一覧を見て機能を把握しようとすると /verify の存在に気づけない。自動起動が止まった今、この不整合は地味に効く。存在を知らないスキルは、手動でも呼べない。
筆者ならこうする
自動で走っていたものが止まったとき、いちばん危ないのは「今までどおり動いているつもり」でいることだ。レビューが走らなくなっても、エラーは出ない。静かに品質ゲートが1つ消える。
筆者なら、まず .claude/agents/code-reviewer.md を用意して @agent-code-reviewer を手癖にする。そのうえで Stop フックを置いて、編集したのにレビューしていないターンを差し戻す。この2つは今日できる。
そして実装役のサブエージェント化は、時間を取って設計する。フックの動作確認を含めて実測してから、また書く。
AI との開発フローの組み方についてはPyInstallerの凍結で落ちたPaddleOCRアプリの制作記事でも扱っている。まずは自分の settings.json に Stop フックが1つも無いかどうか、確認してみてほしい。