Claude CodeのHooksで遊んでみました。
ユーザーの応答待ちのときに通知する、のような制御をしたかったんですが、
何も考えずにやると、ツールの完了時にも通知が鳴ったりしてわずらわしかったので、
ユーザーの応答待ちに入ったときだけに通知する方法を模索してみました
ちなみにMacです
うまくいくと、下記のようにデスクトップ通知+音声通知してくれるようになります。
結論
1.0.41のアプデで簡単になりました
~/.claude/settings.json
{
# ...
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"応答完了しました\" with title \"Claude Code\" subtitle \"タスク完了\"'"
},
{
"type": "command",
"command": "say \"できたぜぇ!?\""
}
]
}
],
}
}
だけです。
以下1.0.40以前のバージョンで無駄に頑張ったやつを供養のため載せときます
deprecated なぜこの仕組みが必要か?
Claude CodeのStopフックは、何らかの処理が完了するたびに作動します。これにはツールの実行完了も含まれるため、何も考えず下記のようにやると通知が過剰になりがちです。
~/.claude/settings.json
{
# ...
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "say \"できたぜぇ!?\""
}
]
}
]
}
}
Claudeアシスタントからの最後のメッセージが「ツール使用(tool_use)」なのか、それとも「テキスト応答(text)」なのかを判別し、「テキスト応答」のときだけ通知を出すようにします。
(余談) Notification Hooksだとどうなるか?
~/.claude/settings.json
{
// ...
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "say \"確認してね!?\"'"
}
]
}
]
}
}
これでもイケると思いきや、通知されるときとされない時があってよくわからないw
deprecated 事前準備:jq のインストール
このスクリプトは、JSONを処理するためのコマンドラインツール jq を利用します。もしインストールしていない場合は、Homebrewを使ってインストールしてください。
brew install jq
deprecated 設定手順
ステップ1: settings.json にフックを追加
まず、Claude Codeの設定ファイルに、処理完了時に特定のスクリプトを呼び出すための設定を追記します。
-
~/.claude/settings.jsonを開きます。 - 以下の
hooksブロックを追記(または既存のhooksにマージ)してください。
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "~/.claude/notify_on_task_completed.sh"
}
]
}
]
}
}
この設定は、「Claudeの処理が停止(Stop)したら、常に指定したコマンド(notify_on_task_completed.sh)を実行する」という意味です。
ステップ2: 通知判定スクリプトを作成
次に、通知を送るかどうかを判断するシェルスクリプトを作成します。
-
~/.claude/ディレクトリにnotify_on_task_completed.shという名前で新しいファイルを作成します。 - 以下のスクリプトをそのまま貼り付けてください。
ステップ3: スクリプトに実行権限を付与
作成したスクリプトが実行できるように、ターミナルで以下のコマンドを実行してください。
chmod +x ~/.claude/notify_on_task_completed.sh
deprecated 仕組みの解説
deprecated 通知条件
このスクリプトの肝はjq を使ってトランスクリプト内の最新アシスタントメッセージの種類をチェックしている部分です。
【ツール実行時のメッセージ構造】
{
"role": "assistant",
"message": {
"content": [{
"type": "tool_use", ← これは無視される
"name": "Read",
"input": {...}
}]
}
}
【ユーザー応答時のメッセージ構造】
{
"role": "assistant",
"message": {
"content": [{
"type": "text", ← これだけが通知対象
"text": "ファイルを作成しました。"
}]
}
}
スクリプトは後者、つまり type が text のメッセージを検知した場合にのみ、通知(osascriptとsay)を実行します。これにより、不要な通知をカットしています。
deprecated 誤検出を防ぐために2段階の検出
通知条件に合致した場合、15s待ってから再度ログを取得し、条件を再確認させています。
# 通知条件に合致しない場合は即座に終了
if [ -z "$LAST_MESSAGE" ]; then
exit 0
fi
# 通知条件に合致した場合のみ待機
sleep 15
# まだ自分のプロセスIDが記録されているか確認
if [ ! -f "$LOCK_FILE" ] || [ "$(cat $LOCK_FILE 2>/dev/null)" != "$$" ]; then
# 他のプロセスに置き換えられた場合は終了
exit 0
fi
# 再度最新のアシスタントメッセージを確認
LAST_MESSAGE=$(tail -50 "$TRANSCRIPT_PATH" | grep '"role":"assistant"' | while read -r line; do
なぜこれをやっているかというと、下記のような、「さあ作りますよ〜」とか、「色々考えてます」、みたいな出力に対しても
【ユーザー応答時のメッセージ構造】
{
"role": "assistant",
"message": {
"content": [{
"type": "text", ← これだけが通知対象
"text": "ファイルを作成しました。"
}]
}
}
↑を満たしてしまい、誤反応してしまうためです。
なので一度条件に合致したら、ちょっと待ち、再度ログを取得して、次のメッセージが来てなければ完了として通知しています。もっとやり方ないの?
いちおう、非同期になってるので本体のClaudeCodeがブロックされることはありません。
自信ないので何かあればご指摘ください🙏
あと、普通にpythonでやればよかった...orz

