0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code ハーネスを「月次スキャン+spec.md」で自己改善させる——type:"prompt" Hook の誤検知を GPT-5.4 が発見した話

0
Posted at

Claude Code ハーネスを「月次スキャン+spec.md」で自己改善させる——type:"prompt" Hook の誤検知を GPT-5.4 が発見した話

はじめに

Claude Code は ~/.claude/settings.json にフック(hooks)を登録することで、特定のイベントをトリガーに任意のシェルコマンドを自動実行できます。
ところが、フックの設定が増えてくると「意図しないタイミングで発火する」誤検知が静かに積み重なっていきます。

本記事では、以下の2つの仕組みを組み合わせてハーネスを自律改善させた事例を紹介します。

  1. 月次スキャン(spec.md レビュー): 30日ごとに設定ファイルを構造レビューし、乖離・劣化を検出する
  2. GPT-5.4 によるコードレビュー: codex-review スキルを使って type: "prompt" フックの誤検知を発見した

Claude Code のフック仕組みおさらい

settings.jsonhooks セクションは次のような構造を持ちます。

{
  "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 で可視化し、未記載フックを自動検出する

ハーネスは作って終わりではなく、定期的に「意図と実装のズレ」をチェックする仕組みを持つことで、長期間にわたって健全に機能し続けます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?