Claude Codeの通知フックで完了を検知する—バックグラウンド実行の受け取り方

Claude Codeの通知フックで完了を検知する—バックグラウンド実行の受け取り方 | mohablog

Claude Code v2.1.198 から、サブエージェントは既定でバックグラウンドに回ります。メインの会話を止めずに別の実装が進む反面、完了や入力待ちに気づけないと放置になりますよね。Notification hook を設定すれば、agent_completedagent_needs_input を OS の通知へそのまま流せます。

目次

バックグラウンド実行が既定になった

変わったのは実行方式です。v2.1.198(2026年7月1日)で、サブエージェントはフォアグラウンド固定からバックグラウンド既定に切り替わりました。現行の最新は v2.1.199

項目v2.1.197までv2.1.198以降
サブエージェントの既定フォアグラウンド(会話をブロック)バックグラウンド(会話を継続)
完了の扱い戻り値がその場で返る完了時に通知イベントが発火
コード作業の後始末手動で commit / PR 作成自動で commit・push・ドラフトPR

Ctrl+B で手動でバックグラウンドへ送っていた頃の運用は Claude CodeのバックグラウンドBash にまとめています。今回はその既定化に伴って要る、完了の受け取り側の話です。

会話を続けている間に何が起きるか

バックグラウンド化で困るのは、終わったタイミングが視界から外れることです。3体を並列に流して別々の実装を進めると、どれがいつ終わったのか、どれが入力待ちで止まっているのかがコンソール上で埋もれます。実際、サブエージェントを3体流して別実装を書いていたとき、1体が確認待ちのまま40分ほど止まっていました。気づいたのは他の作業がひと段落したあと。

止まった瞬間に気づくには、Notification hook を挟みます。

Notification hook で完了と入力待ちを拾う

Claude Code のフックは、特定のイベントで外部コマンドを叩く仕組みです。公式ドキュメントの “Hook events” に一覧があり、その中の Notification が通知系を担当します。フックの型そのものの違いは Claude Code Hooksのprompt型/agent型とは で整理しました。

agent_completed と agent_needs_input の違い

バックグラウンド実行で使うのは、次の通知タイプです。

  • agent_completed: サブエージェントが作業を終えたとき
  • agent_needs_input: 権限確認などでエージェントが人の入力を待っているとき

完了だけ拾うなら前者、止まりに気づきたいなら後者。放置を防ぎたいので、両方まとめて拾います。

settings.json に書く

~/.claude/settings.json かプロジェクトの .claude/settings.json に、通知タイプを matcher として登録します。

{
  "hooks": {
    "Notification": [
      {
        "matcher": "agent_completed|agent_needs_input",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/notify.sh"
          }
        ]
      }
    ]
  }
}

matcher はパイプで複数の通知タイプを OR 指定できます。command は絶対パスか $CLAUDE_PROJECT_DIR 起点で書く。相対パスだと発火時の作業ディレクトリ次第でずれます。

フックが受け取る JSON

フックのコマンドには、標準入力から JSON が渡されます。Notification の場合はこの形。

{
  "session_id": "abc123",
  "hook_event_name": "Notification",
  "cwd": "/path/to/project",
  "transcript_path": "/path/to/transcript.jsonl",
  "message": "通知の本文"
}

使うのは主に message。ここに通知の文面が入るので、そのまま OS の通知本文へ渡します。どのセッションかを区別したいときは session_idcwd も添えられます。

matcher に指定できる通知タイプ

公式の “Matcher patterns” によると、Notification の matcher には次の通知タイプを指定できます。エージェント系の2つ以外にも、権限プロンプトやアイドル、認証成功などが並びます。

permission_prompt
idle_prompt
auth_success
elicitation_dialog
elicitation_complete
elicitation_response
agent_needs_input
agent_completed

権限確認だけ音を鳴らしたいなら permission_prompt 単体、放置検知なら agent_needs_input を足す、という具合に絞れます。

OS ネイティブに通知を出す

#!/usr/bin/env bash
# .claude/notify.sh
# 標準入力の JSON から message を取り出して macOS の通知へ流す
input=$(cat)
msg=$(printf '%s' "$input" | jq -r '.message')

osascript -e "display notification \"$msg\" with title \"Claude Code\" sound name \"Glass\""

このスクリプトを叩くと、サブエージェント完了時に通知センターへ届きます。

Claude Code
作業が完了しました
(Glass の通知音が鳴る)

JSON のパースに jq を使っています。入っていない環境では brew install jq で先に入れておく。

macOS で通知と音を出す

osascriptsound name にサウンド名を渡すと通知音が鳴ります。GlassPing など、/System/Library/Sounds にある名前が使える。音だけ別に鳴らしたいなら afplay /System/Library/Sounds/Glass.aiff でも同じです。

WSL から Windows トーストを出す

WSL で作業しているなら、powershell.exe を呼んで Windows 側のトーストを出します。BurntToast モジュールを入れておくと1行で書ける。

input=$(cat)
msg=$(printf '%s' "$input" | jq -r '.message')

powershell.exe -NoProfile -Command \
  "New-BurntToastNotification -Text 'Claude Code', '$msg'"

PowerShell 側で Install-Module BurntToast を一度実行しておきます。VS Code のターミナルから WSL を使っている場合も、powershell.exe に PATH が通っていればそのまま鳴ります。

Notification・SubagentStop・Stop の使い分け

完了に絡むフックは、発火の粒度が違います。やりたいことで選びます。

フック発火タイミングmatcher主な用途
NotificationClaude Code が通知を出すとき通知タイプ(agent_completed 等)完了・入力待ち・権限要求を外部へ
SubagentStopサブエージェントが1体終わるたびagent_type個別エージェントの後処理
Stopメインの応答が終わるたびなし(常時発火)セッション単位の後始末

通知を人へ飛ばすなら Notification。終わったエージェントの種類ごとに後処理を分けたいなら SubagentStopagent_type でマッチ。応答1往復ごとに必ず走らせたいなら matcher を持たない StopSessionStart hook が開始側なら、Stop は終了側という対応です。

ドラフトPRハンドオフの受け取り方

v2.1.198 では通知だけでなく、成果物の渡し方も変わりました。バックグラウンドのサブエージェントがコード作業を終えると、自動で commit・push し、worktree 上にドラフトPRを開きます。手動の PR 作成が要らなくなった一方、承認前のブランチに push が走る点は把握しておく必要があります。

自動で push される範囲

ハンドオフされるのは、エージェントが作った worktree 上のブランチです。ここまでを自動でやってくれます。

  • 変更のコミット
  • 作業ブランチへの push
  • ドラフト状態での PR 作成

ドラフトなので、そのままマージには進みません。人がレビューして Ready にするまでは、下ごしらえの状態です。

ブランチ保護で暴走を止める

自動 push を野放しにすると、エージェントの中間コミットが保護されていないブランチへ直接届きます。GitHub 側の branch protection を先に固めておく。

# NG: main を保護していない
#  → エージェントの自動 push が main に載りうる

# OK: main への直 push を禁止し、レビュー必須にする
#  → 変更はドラフトPR経由でしか入らない

agent_needs_input を通知に流すようにしてからは、確認待ちで止まった瞬間に気づけます。自動ハンドオフを安全に回すなら、止まったエージェントを放置しない。通知はそのための最低ラインです。

通知が飛ばないとき

設定したのに鳴らない、という詰まり方をしやすいポイントがいくつかあります。

  • matcher の一致: v2.1.195 で、ハイフンを含む matcher が部分一致から完全一致へ変わりました。MCP 由来の名前(mcp__brave-search など)を書いている場合は mcp__brave-search__.* のようにパターンで書きます。
  • jq が無い: message を取り出せず空通知になります。which jq で確認します。
  • command のパス: 相対パスは発火時の cwd 次第でずれます。絶対パスか $CLAUDE_PROJECT_DIR 起点にします。
  • WSL で powershell.exe が見つからない: Windows の PATH が WSL に引き継がれているかを確認します。

フックの構文自体を疑うなら、まず Stopecho fired >> /tmp/hook.log だけ仕込んで、発火が起きているか切り分けます。ログに追記されなければ、通知コマンドではなくフック登録側の問題です。

まとめ

  • v2.1.198 でサブエージェントは既定バックグラウンド。完了と入力待ちは Notification hook で拾う
  • matcher に agent_completed / agent_needs_input を指定し、標準入力の JSON から message を取り出す
  • macOS は osascript、WSL は powershell.exe 経由でトーストや通知音へ
  • 個別の後処理は SubagentStop、セッション終わりは Stop と役割が分かれる
  • ドラフトPR自動ハンドオフは branch protection とセット。保護なしの自動 push は避ける
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次