この記事で得られること: settings.jsonに3つのHookを追加するだけで、テスト忘れゼロ・lint違反ゼロを達成する方法。PreToolUse・PostToolUse・Stopの使い分けと、コピペで導入できる設定テンプレートを公開します。
2026-03-07 追記: 本記事で紹介したHooksベースのテスト自動化は、現在Permissions + deny rulesベースの構成に移行しています。
| 指標 | 導入前 | 導入後 |
|---|---|---|
| テストなしコミット | 週3〜5回 | 0回 |
| lint指摘 | 毎PR 2〜3件 | ほぼ0件 |
| 「テスト書いて」の手動指示 | 1日5〜10回 | 不要 |
| dev-flow完遂率 | 約70% | 約95% |
Claude Code Hooksを使えば、settings.jsonに3つのHookを追加するだけで、AIエージェントのテスト未作成コミットを物理的にブロックできます。この記事では、設定5分で導入できる具体的な手順とコピペ可能なテンプレートを公開します。
AIエージェントの「テスト忘れ」問題 — お願いベースの限界
「テスト書いてって言ったのに、なんでテストなしでコミットしてるの...」
AIエージェントにコードを書かせると、実装そのものはかなり良い。ロジックは合ってるし、型も通る。でもテストを書かずにコミットしたり、lintエラーを無視してPRを出してきたりする。
CLAUDE.mdに「必ずテストを書くこと」と書いても、長い会話の途中でトークン圧縮が走ると、その約束は蒸発する。人間の新人エンジニアなら「テスト書いてからマージリクエスト出してね」で済みますが、AIは3分後にはその指示を忘れています。
お願いベースの品質管理には限界がある。 この壁にぶつかったとき、私たちが出した答えが「仕組みで止める」というアプローチでした。
Claude Code Hooksとは?テスト自動化を実現する仕組み
Claude Code Hooksは、AIエージェントのツール実行に対してプログラム的にフックを差し込む仕組みです。settings.jsonに定義すると、AIの「お願い忘れ」に関係なく100%発火します。
3種類のHookを組み合わせることで、テスト自動化の品質ゲートを構築できます。
| Hook | 発火タイミング | テスト自動化での役割 |
|---|---|---|
| PreToolUse | ツール実行前 | テストなしコミットをブロック |
| PostToolUse | ツール実行後 | 編集のたびに自動lint修正 |
| Stop | エージェント停止時 | 最終テスト実行で品質保証 |
CLAUDE.mdに「テスト書いて」と書くのはお願い。Hookでブロックするのは物理的な壁。お願いの遵守率が体感7割だとしたら、hookは100%です。
手順1:PreToolUseでテストなしコミットをブロックする
最も効果が大きいのがこのHookです。コミット時に「テストファイルが更新されていなければブロック」します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git commit:*)",
"hooks": [
{
"type": "command",
"command": "UNSTAGED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' '); NOTEST=$(find . -name '*.test.*' -newer $(git log -1 --format=%ci HEAD 2>/dev/null || echo '2000-01-01') 2>/dev/null | wc -l | tr -d ' '); if [ \"$NOTEST\" -eq 0 ] && [ \"$UNSTAGED\" -gt 0 ]; then echo '⛔ テストファイルが更新されていません。テストを書いてからコミットしてください' 1>&2; exit 2; fi; exit 0"
}
]
}
]
}
}
exit 2で処理が中断され、AIエージェントに「テストを書いてください」というメッセージが返ります。テストなしコミットがゼロになったのは、このHookで物理的にブロックしているので当然です。
手順2:PostToolUseで編集のたびに自動lint修正する
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint:fix --silent 2>/dev/null; exit 0"
}
]
}
]
}
ファイルを編集するたびに自動でlint修正が走ります。1回1回は小さな修正ですが、lint警告が積もり積もって最後にまとめて直す地獄を防げます。PRレビューでのlint指摘がほぼゼロになった最大の要因がこのHookです。
手順3:Stop Hookで最終テストを自動実行する
{
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$PWD\" && npm test --silent 2>&1 | tail -5; EXIT=$?; if [ $EXIT -ne 0 ]; then echo '❌ テストが失敗しています。修正してから完了してください' 1>&2; exit 1; fi; exit 0"
}
]
}
]
}
AIエージェントが「完了しました」と言って停止する瞬間に、テストを全件実行します。1件でも失敗していたら停止を許可しない。人間が「テスト通った?」と確認する前にプログラムがチェックしてくれるので、レビューの心理的負荷がかなり軽くなります。
3つのHookを組み合わせた品質ゲートの全体像
3つのHookを設定すると、開発フロー全体で品質が自動的に担保されます。
| 層 | 仕組み | 守備範囲 |
|---|---|---|
| Layer 1 | Hook(settings.json) | 個別操作の瞬間チェック |
| Layer 2 | Skill(dev-validate) | ワークフローの節目チェック |
| Layer 3 | Subagent(Task委譲) | コンテキスト節約+専門チェック |
Layer 1: Hook — 条件反射的なガードレール
Hookは無条件で発火するのがポイントです。AIの判断を介さない。3つが常時稼働していれば、「テストなしでコミットされた」事故はゼロになります。ただし、Hookは軽い処理に向いています。複雑なテスト実行や問題の切り分けはHookの守備範囲外です。
Layer 2: Skill — ワークフロー内のチェックポイント
dev-kickoffの6フェーズ開発フローの中で、dev-validateはPhase 4に位置します。
Phase 1: git-prepare(worktree作成)
↓
Phase 2: dev-issue-analyze(要件分析)
↓
Phase 3: dev-implement(実装)
↓
Phase 4: dev-validate --fix ← ここ
↓
Phase 5: git-commit(コミット)
↓
Phase 6: git-pr(PR作成)
実装が終わったら即座にdev-validateが走り、テスト失敗やlintエラーがあればPhase 3に差し戻し。--fixオプション付きなので、自動修正可能な問題はその場で直してくれます。
Layer 3: Subagent — コンテキストを消費しない専門エージェント
テスト実行のログ出力は長い。テストが20件あれば、そのログだけでコンテキストウィンドウの相当な部分を消費します。
メインエージェント(dev-kickoff)
│
├── Phase 3: 実装に集中
│
└── Phase 4: Task(quality-engineer) に委譲
└── dev-validate --fix --worktree $PATH
├── テスト実行(ログはsubagentのcontextに収まる)
├── lint実行
└── 結果JSONだけメインに返す
メインエージェントのコンテキストを汚さずに、品質チェックの結果だけを受け取る。テストが100件あっても、メインに返るのは「pass」か「fail」かの小さなJSONだけです。
dev-validateスキル:テスト自動化のエンジン
Hookが「いつ検証するか」を制御する門番なら、dev-validateは「何を検証するか」を担当するエンジンです。
$SKILLS_DIR/dev-validate/scripts/validate.sh [--fix] [--strict] [--worktree <path>]
| オプション | 説明 |
|---|---|
--fix | lint問題を自動修正 |
--strict | lintがスキップされたら失敗扱い |
--worktree | 検証対象のworktreeパス |
内部ではプロジェクトタイプを自動検出して、適切なコマンドを実行します。
# テスト実行:プロジェクトの設定ファイルで言語を自動判定
run_tests() {
if [[ -f "package.json" ]]; then
if grep -q '"test"' package.json 2>/dev/null; then
local pm="npm"
[[ -f "yarn.lock" ]] && pm="yarn"
[[ -f "pnpm-lock.yaml" ]] && pm="pnpm"
$pm test >/dev/null 2>&1 && TEST_RESULT="passed" \
|| { TEST_RESULT="failed"; TEST_EXIT=1; }
else
TEST_RESULT="no_test_script"
fi
elif [[ -f "Cargo.toml" ]]; then
cargo test >/dev/null 2>&1 && TEST_RESULT="passed" \
|| { TEST_RESULT="failed"; TEST_EXIT=1; }
elif [[ -f "go.mod" ]]; then
go test ./... >/dev/null 2>&1 && TEST_RESULT="passed" \
|| { TEST_RESULT="failed"; TEST_EXIT=1; }
elif [[ -f "pytest.ini" ]] || [[ -f "pyproject.toml" ]]; then
pytest >/dev/null 2>&1 && TEST_RESULT="passed" \
|| { TEST_RESULT="failed"; TEST_EXIT=1; }
fi
}
出力はJSON形式で、Hookやワークフローから判定しやすい設計です。
cat <<JSONEOF
{
"worktree": "$WORK_DIR",
"changes": {"files": $FILES_CHANGED, "insertions": $INSERTIONS, "deletions": $DELETIONS},
"tests": "$TEST_RESULT",
"lint": "$LINT_RESULT",
"overall": "$OVERALL",
"exit_code": $EXIT_CODE
}
JSONEOF
overall: "fail"なら先に進まない、tests: "failed"ならテスト修正に戻る。フルソースはGitHubリポジトリで公開しています。
コピペで使えるsettings.jsonテンプレート
ミニマル構成(個人開発・小規模プロジェクト向け)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git push:*)",
"hooks": [
{
"type": "command",
"command": "BR=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo ''); case \"$BR\" in dev|develop|main|master) echo '⛔ 保護ブランチへの直接pushはブロックされています' 1>&2; exit 2;; esac; exit 0"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint:fix --silent 2>/dev/null; exit 0"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "npm test --silent 2>&1 | tail -3; EXIT=$?; if [ $EXIT -ne 0 ]; then echo '❌ テスト失敗' 1>&2; exit 1; fi; exit 0"
}
]
}
]
}
}
これだけで「保護ブランチpushブロック + 自動lint + 停止時テスト」の3点セットが稼働します。
フル構成(チーム開発・Skill連携込み)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git push:*)",
"hooks": [
{
"type": "command",
"command": "BR=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo ''); case \"$BR\" in dev|develop|main|master) echo '⛔ 保護ブランチへの直接pushはブロックされています' 1>&2; exit 2;; esac; exit 0"
}
]
},
{
"matcher": "Bash(git commit:*)",
"hooks": [
{
"type": "command",
"command": "RESULT=$($SKILLS_DIR/dev-validate/scripts/validate.sh 2>/dev/null); OVERALL=$(echo \"$RESULT\" | grep -o '\"overall\":\"[^\"]*\"' | cut -d'\"' -f4); if [ \"$OVERALL\" = 'fail' ]; then echo '⛔ dev-validate失敗。テスト/lintを修正してください' 1>&2; echo \"$RESULT\" 1>&2; exit 2; fi; exit 0"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm run lint:fix --silent 2>/dev/null; exit 0"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "RESULT=$($SKILLS_DIR/dev-validate/scripts/validate.sh --strict 2>/dev/null); OVERALL=$(echo \"$RESULT\" | grep -o '\"overall\":\"[^\"]*\"' | cut -d'\"' -f4); if [ \"$OVERALL\" = 'fail' ]; then echo \"$RESULT\" 1>&2; echo '❌ 品質チェック失敗。修正してから完了してください' 1>&2; exit 1; fi; exit 0"
}
]
}
]
},
"permissions": {
"deny": ["Bash(rm -rf:*)", "Bash(sudo:*)", "Bash(git push --force:*)"]
}
}
フル構成では、PreToolUseのコミット前チェックにdev-validateスキルを直接呼び出し、Stop Hookでも--strictオプション付きで実行する厳格モードです。
Hookカスタマイズのポイント
matcherのパターン設計
matcherは「どのツール実行にhookを適用するか」を指定します。
"Bash(git commit:*)" → git commitコマンドにマッチ
"Bash(git push:*)" → git pushコマンドにマッチ
"Write|Edit" → ファイル書き込み・編集にマッチ
"Notebook.*" → NotebookRead, NotebookEdit等にマッチ
"mcp__memory__.*" → MCPサーバーのツールにマッチ
コミット・プッシュ系は個別指定、編集系はまとめてマッチが実用的なバランスです。
exit codeの使い分け
| exit code | 意味 | AIエージェントの挙動 |
|---|---|---|
| 0 | 成功(続行) | そのまま処理を続ける |
| 1 | 警告(レポート) | 警告メッセージを認識するが続行 |
| 2 | ブロック(中断) | 処理を中断し、エラー内容を報告 |
PreToolUseでexit 2を返すと、ツール実行そのものが中断されます。PostToolUseの場合はツール実行済みなので、exit 2は「結果に問題がある」というシグナルになります。
導入してわかった予想外の効果
- PostToolUseのlint:fixが思ったより効く: 1回1回は小さな修正。でもPR作成時のlintエラーがほぼゼロになった
- Stop Hookの心理的安全性: 「AIが完了と言ったならテスト通ってる」と信頼できるようになった
- Subagent委譲のコンテキスト節約: テストが多いプロジェクトでは顕著。メインエージェントが実装に集中できる
まだ課題として残っていること
- Hookの実行時間: validate.shをフルで走らせると数十秒かかるプロジェクトがある
- matcherの設計: プロジェクトごとにツール名が違う場合の対応が途上
- 偽陽性の管理: 外部API依存テストがHookで落ちて、関係ない修正がブロックされることがある
まとめ
Claude Code Hooksを使ったテスト自動化は、「AIにテストを書かせる」のではなく「テストを書かないと先に進めない構造にする」 というアプローチです。
「お願い」で品質を担保する時代は終わりました。AIエージェントとの共同作業で品質を維持するには、人間のコードレビューと同じく、仕組みで担保するのが現実的です。
まずはミニマル構成のsettings.jsonテンプレートから試してみてください。設定に5分、効果は半永久。保護ブランチpushブロックだけでも、「テスト通ったっけ...」の心配が1つ消えます。
なお、本記事で紹介したHooksベースの品質ゲートは、その後Permissions + deny rulesベースの構成に移行しました。
あわせて読みたい
- 【2026年版】AIコーディングツール完全比較 — Claude Code・Codex・Antigravityの選び方
- 【Claude Code】settings.json完全ガイド — CLAUDE.md・Hooks・permissionsの設定を深掘り
- 【Claude Code × Codex × Antigravity】AIコーディングツールのSkillsを統一管理する方法 — Skills共有の実装方法
→ 気軽に相談する
Claude Code エージェント・安全設計 完全ガイド — この記事を含む10本の記事で、エージェント活用・Hooks安全設計・並列開発を体系的に解説しています。



