🔔

Claude CodeのHooksで作業完了と指示待ちを音と通知で知らせる+応用

はじめに

Claude Codeに作業を依頼すると大小時間がかかるので、待ち時間に別の作業をしたくなります。 その際に、作業が完了したときに通知がなってくれるとタイムロスが少なくなり助かります。 Claude CodeのHooksという機能を使ってClaude Codeが「作業を完了したとき」、「作業の途中でユーザに確認を求めて停止しているとき」に音を鳴らす方法があるので、紹介します。

また、応用でVSCodeやターミナルなど複数のアプリケーション上からClaude Codeを立ち上げて動かしたときどのアプリケーションからの通知なのかがわかるようにする方法についても記録します。

Claude CodeのHooksとは

公式から引用すると以下のように定義されております。

フックは、Claude Codeのライフサイクル内の特定のポイントで自動的に実行されるユーザー定義のシェル コマンド、HTTP エンドポイント、または LLM プロンプトです。

参照:https://code.claude.com/docs/ja/hooks

ざっくり言い換えると、Hooksは特定イベントで任意コマンドを実行できる仕組みです。 今回使うのはその中でも2つのイベントStop(応答完了時)とNotification(ユーザーの入力・許可待ち時)です。

音と通知を鳴らす基本設定

実際に使っている ~/.claude/settings.jsonhooks セクションは次のとおりです。

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Glass.aiff; osascript -e 'display notification \"作業が完了しました\" with title \"claude code\"' 2>/dev/null || true",
            "async": true
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Funk.aiff; osascript -e 'display notification \"確認が必要です\" with title \"claude code\"' 2>/dev/null || true",
            "async": true
          }
        ]
      }
    ]
  }
}

音声ははmacOS標準で搭載されている音声を流用します。(/System/Library/Sounds/*.aiff)

音声は種別によって分けることで、音だけで終わったのか確認したいことがあるのかを判断できます。

  • Stop: afplay /System/Library/Sounds/Glass.aiffの音 + osascriptで通知センターに「作業が完了しました」と表示
  • Notification: afplay /System/Library/Sounds/Funk.aiffの音 + osascriptで通知センターに「確認が必要です」と表示

osascriptのdisplay notificationでmacの通知センターにも出るようにしています。

応用: 複数のClaudeを並行で動かすと「どれが終わったか」問題

複数のアプリケーションでClaude Codeを走らせると、どのアプリケーションからの通知なのかがわからなくなりがちです。

そこで応用として、どのアプリケーションからの通知なのかがわかるように通知をクリックするとその通知元アプリケーションに遷移できるようにしたいと思います。

私は、VSCodeの拡張機能のClaude Code、VSCode上のターミナルで動かしているClaude Code、ターミナルアプリWarp、macOS標準のターミナルを使うことがあるので、今回はその環境でのサンプルを紹介します。

どのプロジェクトか通知に出す

どのClaude Codeかを見分けるには、フックのコマンドに標準入力で渡されるJSONを使用します。ここには発火時の作業ディレクトリ cwd やセッションID session_id が含まれていて、jq で取り出せます。

作業ディレクトリの末尾(basename)をプロジェクト名として通知タイトルに入れると、どのClaude Codeが鳴ったのかがひと目で分かります。

INPUT="$(cat)"
CWD="$(printf '%s' "$INPUT" | jq -r '.cwd // empty')"
PROJECT="$(basename "${CWD:-Claude}")"   # 例: blog
TITLE="Claude: $PROJECT"

通知テスト(downloadsフォルダで実行)

(macOS標準ターミナル・Downloadsフォルダで実行)

settings.json にコマンドを直書きすると読みにくくなるので、~/.claude/hooks/notify.sh のような外部スクリプトに切り出して呼ぶ形にしておきます。

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "$HOME/.claude/hooks/notify.sh stop", "async": true }
        ]
      }
    ]
  }
}

クリックで該当ウィンドウへ遷移する

通知をクリックして該当ウィンドウへ遷移したいのですが、ここで一つ壁があります。標準の osascript -e 'display notification ...' は通知を出せるものの、クリックしたときの動作を指定できません。

そこで terminal-notifier を使います。-execute オプションに、クリックされたときに実行するコマンドを渡せます。

brew install terminal-notifier

VSCodeの拡張機能でも統合ターミナルでも、遷移先は「そのワークスペースを開いているVSCodeウィンドウ」です。code "$CWD" を実行すると、その作業ディレクトリを開いている既存ウィンドウが前面に来ます(開いていなければ新しく開きます)。

# 注意: -execute は最小PATHのシェルで実行されるため、code は絶対パスで指定する
CODE_BIN="$(command -v code)"

terminal-notifier \
  -title "$TITLE" \
  -message "作業が完了しました" \
  -group "claude-$PROJECT" \
  -execute "$CODE_BIN '$CWD'"

-group に同じ値を渡すと、同じプロジェクトの古い通知が新しいものに置き換わり、通知センターが散らかりません。

Warpはこの方式とは別で、AppleScriptに対応していない代わりにネイティブの通知機構を持っています。claude-code-warp プラグインを入れておくと、Warpのタブに紐づいた通知が出て、クリックするとそのタブへ遷移します。Warpを使う場合はこちらに任せるのがシンプルです。

まとめ

  • Stop / Notificationに音+通知を割り当てるだけで待ち時間のストレスが激減します。
  • 応用としてcwdで判別 + terminal-notifierでクリック遷移まで実現できた

参考文献

新着記事

関連ネットワーク(beta)

ドラッグで移動 / Ctrl+ホイールでズーム