皆さん、よきAI駆動開発をお送りになっていますでしょうか?
私は数カ月前にClaude Codeを触り始めたのですが、本格的に使い始めたときに感じたことがあります。
「許可待ち人間ならぬ許可待ちAIじゃん」
Claude Code を回しているとき、作業が知らぬ間に止まっていてフラストレーション感じる点の中に以下の2点が挙げられます。
- 「許可待ち(権限が必要)に気づかず止まる」問題
- 「応答完了に気づかず待つ」問題
そこで、ずんだもんに言わせます。
- 許可が必要 → 「実行してもいいのだ?」
- 応答完了 → 「応答完了なのだ!」
このアイデアはもうすでに目にしている方、使われている方もいらっしゃるかと思います。
以前、ZennでVOICEVOXを通して、mcpやhooksで通知させている方がいらっしゃったのですが、自分の環境ではバージョンなどの関係か動作しなかったので、ゼロからClaude Codeに聞きながら設定してみました。
- 今回は VOICEVOX 常駐なし!
- WAV(または m4a)を作成して afplay で鳴らすだけなので軽い!
また設定しながら曖昧に理解していたhooksについても学んでみたので、まとめてみました。
ぜひご覧ください!
📌 TL;DR
- ずんだもん音声ファイル(例:
permission.wav/done.wav)を用意して置く(プリビルド) -
~/.claude/settings.jsonに Notification と Stop の hook を追加する - Notification は message を見て「権限が必要」のときだけ鳴らす(アイドル通知では鳴らさない)
Claude Code hooks は、ライフサイクルの特定タイミングで 登録したコマンドを自動実行し、入力は stdin の JSONで渡されます。
📚 目次
- hooks(フック)とは?
- 対応している hook イベント一覧
- 設定ファイルの場所と優先順位
- ずんだもん報告 hooks
- hooksおすすめカスタマイズ例
- トラブルシューティング
- さいごに
- 参考リンク
hooks(フック)とは?
hooks(フック)は、Claude Code のイベントに合わせて 任意のコマンドを自動実行する仕組みです。
「プロンプトでお願いする」のではなく、仕組みで強制できるのがいいところですね。
余談ですが、ずっとhooks/フックスと読んでいましたが、jaのレファレンスが出て「フック」と表記されているので日本語ではフックと呼ぶのが正しいのでしょうか。すでにhooksが浸透している気がしますが…。
対応している hook イベント一覧
以下が公式リファレンスの代表的なイベント群です。
改めて調べるとサブエージェント完了など細かいところで設定できるのは便利だと思いました!
- PreToolUse: ツール実行前(ブロック/制御が強い)
- PostToolUse: ツール実行後(整形・テスト・ログなど)
- Notification: Claude Code の通知(権限が必要/入力待ち60秒)
- UserPromptSubmit: ユーザー送信直後(プロンプト検証・追記)
- Stop: 応答完了(ユーザー割り込み停止時は走らない)
- SubagentStop: サブエージェント完了
- PreCompact: compact 前
- SessionStart / SessionEnd: セッション開始/終了
ちなみに「どのツール(例:Bash, Write, Edit など)呼び出しに対してこのフックを適用するか」を絞るmatcher が使えるのは PreToolUse / PostToolUse だけです。
ツール名で絞れないイベント(Notification/Stop など)では省略されます。
設定ファイルの場所と優先順位
hooks は設定ファイル(JSON)で管理します。
-
~/.claude/settings.json… ユーザー設定 -
.claude/settings.json… プロジェクト設定 -
.claude/settings.local.json… ローカルプロジェクト設定(コミットしない) (Claude Code)
下に行くほど優先順位が高く、設定が上書きされていきます。
そのため新しく設定を追加する際は、まず .claude/settings.local.json で試すのが良いですね。
また設定する際はコマンドの/hooksを使うとjson形式で事故りにくく、もしClaude Code側の設定が変わっていても、公式の設定(json形式)で作ってくれるのでおすすめです。
ずんだもん報告 hooks
今回の目的
では実際に今回実装する「ずんだもん完了報告hooks」を設定していきます!
設定するのは以下の通りです。
-
Notification: 「権限が必要」にだけ反応して呼び戻す。→ 「実行してもいいのだ?」
- ※Notification は「権限が必要」以外に「入力待ち60秒」でも飛ぶようなので、今回は
messageを見てフィルタしています。
- ※Notification は「権限が必要」以外に「入力待ち60秒」でも飛ぶようなので、今回は
- Stop: 応答完了時に呼び出す→「実行完了なのだ!」
それでは実際の手順に進みます。
0) 音声ファイルを配置する(プリビルド)
例として、以下に置きます。
音声ファイルはVOICEVOXのずんだもんを使いました。
作成の仕方は驚くほど簡単なので割愛します!
~/.claude/hooks/sounds/
permission.wav
done.wav
※WAV が重いなら m4a でも良いです。afplay が再生できる形式に寄せてください。
1) フックスクリプトを作る(長い bash を settings.json に埋めない)
絶対にこうしないといけない、というわけではありませんが、settings.jsonが長大になり、可読性・保守性が低下してしまうのでスクリプトは分けることを推奨しておきます。
こまかなフィルタリング設定の変更なども、分けておいたほうがしやすいです。
~/.claude/hooks/play.sh
#!/usr/bin/env bash
set -euo pipefail
SOUND_PATH="${1:-}"
LOG="${CLAUDE_HOOK_LOG:-/tmp/claude_hooks.log}"
if [[ -z "$SOUND_PATH" ]]; then
echo "$(date): play.sh: SOUND_PATH is empty" >> "$LOG"
exit 0
fi
# 連打対策(同一音を短時間に連続再生しない)
LOCK="/tmp/claude_sound_$(basename "$SOUND_PATH").lock"
NOW="$(date +%s)"
if [[ -f "$LOCK" ]]; then
LAST="$(stat -f %m "$LOCK" 2>/dev/null || echo 0)"
if (( NOW - LAST < 2 )); then
exit 0
fi
fi
touch "$LOCK"
echo "$(date): play: $SOUND_PATH" >> "$LOG"
afplay "$SOUND_PATH" >> "$LOG" 2>&1 || true
~/.claude/hooks/notify-permission.sh(Notification の message を見て「権限が必要」だけ鳴らす)
#!/usr/bin/env bash
set -euo pipefail
LOG="${CLAUDE_HOOK_LOG:-/tmp/claude_hooks.log}"
SOUND="${HOME}/.claude/hooks/sounds/permission.wav"
# stdin(JSON)から message を抜く(Claude Code hooks は stdin に JSON を流す) :contentReference[oaicite:13]{index=13}
MSG="$(python3 - <<'PY'
import json, sys
try:
d = json.load(sys.stdin)
except Exception:
print("")
sys.exit(0)
print(d.get("message","") or "")
PY
)"
echo "$(date): Notification message: ${MSG}" >> "$LOG"
# ここが肝:Notification は「権限が必要」以外もあるのでフィルタ :contentReference[oaicite:14]{index=14}
# 例の文言(JP/EN)に寄せて判定。必要ならここを増やす。
if echo "$MSG" | grep -Eq "権限が必要|permission"; then
"${HOME}/.claude/hooks/play.sh" "$SOUND"
fi
~/.claude/hooks/notify-done.sh(Stop で鳴らす)
#!/usr/bin/env bash
set -euo pipefail
SOUND="${HOME}/.claude/hooks/sounds/done.wav"
"${HOME}/.claude/hooks/play.sh" "$SOUND"
実行権限:
chmod +x ~/.claude/hooks/*.sh
2) settings.json を置き換える
~/.claude/settings.json(必要なら .claude/settings.local.json):
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "bash -lc '$HOME/.claude/hooks/notify-permission.sh'",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash -lc '$HOME/.claude/hooks/notify-done.sh'",
"timeout": 10
}
]
}
]
}
}
ポイント:
- Notification は「権限が必要」でも飛ぶ(Claude Codeが通知を送るとき≒ユーザーに権限・許可を求めるとき)
- Notification の入力 JSON には
messageが入るので、それで分岐できる - matcher は Notification/Stop には効かないので省略がOK
ちなみにすでにhooksを設定されている方は、以下の2パターンで並べて書くことができます。
1) 同じ matcher ブロック内で hooks を複数並べる
同一イベント・同一条件で、複数コマンドを実行できます。
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{ "type": "command", "command": "hogehoge" },
{ "type": "command", "command": "fugafuga" }
]
}
]
}
}
2) 同じイベントに matcher 別のブロックを複数書く
通知タイプごとに、鳴らす音や処理を分岐できます。
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{ "type": "command", "command": "hogehoge" }
]
},
{
"matcher": "idle_prompt",
"hooks": [
{ "type": "command", "command": "fugafuga" }
]
}
]
}
}
こうして、複数の hook が同じイベントにマッチした場合、並行実行されます。
またプラグイン由来の hooks も、ユーザー/プロジェクト設定と マージされ、同じイベントに複数反応します。
hooksおすすめカスタマイズ例
以上がhooks解説と、ずんだもん報告hooksの実装でした。
ここからは、おすすめのhooksを少し紹介します!
1) 「権限が必要」検知を堅くする
Notification が投げる文言は将来変わる可能性があります。
その場合に備えて、条件を増やします:
if echo "$MSG" | grep -Eq "権限が必要|permission|requires.*permission|needs.*permission"; then ...
※ Notification は「入力待ち60秒」も含むので、“権限”だけに寄せるのが運用上は楽だと思いますが、適宜書き換えると良いでしょう。
2) Stop 連打(サブエージェントや連続応答)を抑える
Stop/SubagentStop 周りは “続行” と組み合わせると無限ループになり得るので、公式も stop_hook_active のチェックを推奨しているようです。
今回は単純再生だけですが、将来 Stop で “続行” をする場合、必ずチェックしてください。
3) 事故防止のおすすめ hooks
A. 危険コマンドを止める:PreToolUse(Bash)
PreToolUse は「ツール実行前」で、終了コード2や JSON 出力でブロック制御できます。
例(雑に rm -rf を弾く場合):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 - <<'PY'\nimport json,sys\nx=json.load(sys.stdin)\ncmd=(x.get('tool_input') or {}).get('command','')\nif 'rm -rf' in cmd:\n print('危険コマンド検知: rm -rf は手動ですること', file=sys.stderr)\n sys.exit(2)\nsys.exit(0)\nPY"
}
]
}
]
}
}
もし危険なコマンドを実行しようとしていたら、終了コード2の挙動(PreToolUse はツール呼び出し自体をブロック)をします。
B. Linter/formatter を “勝手に” 走らせる:PostToolUse(Write/Edit)
「Claude が編集した直後にフォーマッタを走らせる」は PostToolUse が鉄板です。
例(.ts/.js のときだけ prettier):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "python3 - <<'PY'\nimport json,sys,subprocess\nx=json.load(sys.stdin)\npath=(x.get('tool_input') or {}).get('file_path','')\nif path.endswith(('.ts','.js')):\n subprocess.call(['prettier','--write',path])\nPY"
}
]
}
]
}
}
formatter がファイルを書き換えると、その結果としてさらに hooks が走る場合があります。
その場合は CLAUDE_HOOK_RUNNING=1 を付けるなど、再入防止フラグを入れるのが安全だと思います。
トラブルシューティング
自分がhooksで通知設定する際に困ったときの解決法を共有します。
hooks が走ってるか分からない
Claude Codeに聞きつつ、ログに吐かせて確認します(例: /tmp/claude_hooks.log)。
JSON の文法が壊れてる
以下のコマンドで確認します。意外と自分で直書き設定するとよくありました(なので簡単な設定は/hooksからするのがおすすめです)。
python3 -m json.tool ~/.claude/settings.json > /dev/null && echo "settings.json OK"
Notification で鳴らない
Notification は stdin の message を見て分岐しているので、まずログに message を出して実際の文言を確認します。
Notification 自体の仕様として「権限が必要」or「入力待ち60秒」で通知が飛びます。
最初ゼロから、試行錯誤していたときは通知の種類を理解していないことにより、うまく行かないときがありました。
さいごに
最後にですがセキュリティ注意も書いておきます。
改めて、hooks は 任意のシェルコマンドを自動実行します。
とても便利ですが、Claude Codeのリファレンスにあるように、「責任は設定者にある」「データ損失やシステム損害の可能性がある」「安全な環境でテストせよ」と意識した方が良さそうです。チームで使う場合やプロジェクト設定にhooksを入れる場合、運用前にコマンドは必ずレビューしましょう。
hooksでは、今回のように開発が効率化され、ノンフラストレーションな環境を構築するだけでなく、AIに好き勝手されないようにすることもできます。
Claude Codeには色々開発に便利な機能が追加されているので、今後も目が話せないですね。
それでは、良きAI駆動開発ライフを!