Claude Code ハーネスを「月次スキャン+spec.md」で自己改善させる——type:"prompt" Hook の誤検知を GPT-5.4 が発見した話
はじめに
Claude Code は ~/.claude/settings.json にフック(hooks)を登録することで、特定のイベントをトリガーに任意のシェルコマンドを自動実行できます。
ところが、フックの設定が増えてくると「意図しないタイミングで発火する」誤検知が静かに積み重なっていきます。
本記事では、以下の2つの仕組みを組み合わせてハーネスを自律改善させた事例を紹介します。
- 月次スキャン(spec.md レビュー): 30日ごとに設定ファイルを構造レビューし、乖離・劣化を検出する
-
GPT-5.4 によるコードレビュー:
codex-reviewスキルを使ってtype: "prompt"フックの誤検知を発見した
Claude Code のフック仕組みおさらい
settings.json の hooks セクションは次のような構造を持ちます。
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "git add -A",
"hooks": [
{ "type": "command", "command": "echo 'git add -A は禁止' && exit 1" }
]
}
]
}
}
フックには複数の type があります。
| type | 動作 |
|---|---|
command |
シェルコマンドを実行。exit 1 で Claude の処理を中断できる |
prompt |
Claude 自身に追加プロンプトを差し込む(ここが罠) |
type: "prompt" は「ユーザーの入力に指示を上書きしたい」ときに使います。たとえば「git push を含む指示が来たら、本当に意図しているか確認させる」といった使い方が想定されます。
誤検知が生まれた経緯
筆者の環境では、スケジュールタスクの自動起動やスキル管理の一連のフローを「ハーネス」と呼んで運用しています。
ある月次スキャンで、UserPromptSubmit に登録した type: "prompt" フックが 意図しない文字列パターンにも反応する 問題を発見しました。
具体的には、以下のような matcher を設定していました。
{
"matcher": "deploy",
"hooks": [
{
"type": "prompt",
"prompt": "デプロイは staging 環境で先に確認しましたか?確認済みなら続行してください。"
}
]
}
この設定の意図は「deploy という単語を含むプロンプトが来たらデプロイ確認を促す」ことです。
しかし実際には、以下のような 一切関係ない文脈でも発火 していました。
- 「Cloudflare Workers のデプロイメント戦略を教えて」
- 「deploy hook の動作を確認したい」
- 「
vercel deployのドキュメントURL を調べて」
type: "prompt" は type: "command" と違い、追加プロンプトが静かに差し込まれるだけなので、ユーザーが気づきにくいのが厄介です。Claude の返答がわずかに変わっても「そういうものか」と流してしまいます。
GPT-5.4 が誤検知を発見した話
月次スキャンのフローでは、/codex-review スキルを使って settings.json の差分を GPT-5.4 にレビューさせています。
# codex-review スキルの呼び出し例
cd ~/.claude
codex --model gpt-5.4 "settings.json の hooks セクションをレビューし、誤検知リスクのある matcher を指摘してください"
GPT-5.4 は以下のようなレポートを出力しました(要約)。
[HIGH] matcher: "deploy" — 部分一致のため、単語 "deploy" を含む
あらゆるプロンプトに発火します。
意図する対象が "実際のデプロイ操作" であれば、
より限定的な matcher(例: "git push", "vercel deploy", "wrangler deploy")
に分割することを推奨します。
[MEDIUM] matcher: "reset" — "reset" という単語は設定変更の文脈でも
会話で日常的に登場します。type: "command" で exit 1 する場合は
誤ブロックのリスクを考慮してください。
この指摘をもとに、deploy の matcher を具体的なコマンド名に分割しました。
{
"hooks": {
"UserPromptSubmit": [
{ "matcher": "vercel deploy", "hooks": [{ "type": "prompt", "prompt": "vercel deploy 実行前に staging 確認をしましたか?" }] },
{ "matcher": "wrangler deploy", "hooks": [{ "type": "prompt", "prompt": "wrangler deploy 実行前に staging 確認をしましたか?" }] },
{ "matcher": "git push --force", "hooks": [{ "type": "command", "command": "echo '強制プッシュは禁止' && exit 1" }] }
]
}
}
spec.md による月次スキャン設計
誤検知を防ぐ継続的な仕組みとして、「変更意図の spec.md」 をフックと並べて管理しています。
~/.claude/
settings.json ← 実設定
docs/
hooks-spec.md ← 各フックの「なぜ設定したか」「想定発火パターン」「禁止する操作の定義」
hooks-spec.md には以下を記述します。
## UserPromptSubmit / vercel deploy
**目的**: vercel deploy コマンドを含む指示が来たとき、本番デプロイ前に staging 確認を促す
**想定発火**:
- "vercel deploy" を含むプロンプト
**非発火であるべきケース**:
- "vercel の deploy オプションを調べて"(調査目的)
**現在の実装の限界**:
- matcher は "vercel deploy" 完全一致(大文字小文字無視)
- 調査文脈での誤検知は0件(2026-04-01 〜 2026-04-30 観測)
月次スキャンではこの spec.md と settings.json の diff を取り、「仕様書に書いていないフックが増えていないか」「30日以上更新されていないフックが実態と合っているか」をチェックします。
実装パターン:月次スキャンの自動化
スケジュールタスクとして登録するスキャンの骨格を示します。
import json
from pathlib import Path
from datetime import datetime, timedelta
SETTINGS = Path.home() / ".claude" / "settings.json"
SPEC = Path.home() / ".claude" / "docs" / "hooks-spec.md"
def load_hooks(settings_path: Path) -> list[dict]:
data = json.loads(settings_path.read_text())
return data.get("hooks", {})
def find_unspecced_hooks(hooks: dict, spec_text: str) -> list[str]:
"""spec.md に記載のないフックを検出する"""
unspecced = []
for event, entries in hooks.items():
for entry in entries:
for hook in entry.get("hooks", []):
matcher = entry.get("matcher", "")
if matcher and matcher not in spec_text:
unspecced.append(f"{event} / {matcher}")
return unspecced
hooks = load_hooks(SETTINGS)
spec_text = SPEC.read_text() if SPEC.exists() else ""
issues = find_unspecced_hooks(hooks, spec_text)
if issues:
print("仕様書未記載のフックが見つかりました:")
for issue in issues:
print(f" - {issue}")
まとめ
-
type: "prompt"フックは 静かに差し込まれる ため、誤検知に気づきにくい -
matcherは 具体的なコマンド名 まで絞り込む(単語単位は誤検知のもと) - フックと並行して spec.md(変更意図の仕様書) を管理することで、月次スキャンが機能する
- GPT-5.4 などの外部レビュアーに
settings.jsonを定期レビューさせると、自分では気づかない誤検知パターンを発見できる - スキャン結果は
settings.jsonとの diff で可視化し、未記載フックを自動検出する
ハーネスは作って終わりではなく、定期的に「意図と実装のズレ」をチェックする仕組みを持つことで、長期間にわたって健全に機能し続けます。