Shimabox Blog

~sit in the sun~

Claude Code/Hooks × osascript/terminal-notifier で承認待ち通知を受け取る

Claude Code/Hooks × osascript/terminal-notifier で承認待ち通知を受け取るはてなブックマーク

はいさい!しまぶだよ。

Claude Codeを使っていると、ツールの実行時に承認を求められることがあります。コードを書いている間は画面を見ているのでいいのですが、ちょっと席を外したり、別の作業(会議とか会議とか)をしたりしていると承認待ちになっていることに気付けません。それはとても悲しいことですし、かといってずっと画面に張り付いているわけにもいきません。

最初はSlackに通知を飛ばすことを考えたのですが、そこまで大げさにしなくてもいいのかなと。もっとカジュアルに自分だけが分かるものでいいんじゃないのかなと。

そこで、macOSのローカル通知で十分じゃないかと思いまして、色々と試した結果をメモしておきます。このあたりの通知周りはすでに何かベストプラクティスがあるような気がするのですが、せっかくなので。

最初に結論

notification demo

terminal-notifier を使って、通知を表示しています。クリックするとターミナルがアクティブになるので、すぐに承認操作ができますし、アイコンもカスタム可能で便利です。

Claude CodeのHooks機能

調べてみると、Claude Codeにはhooks機能があり、特定のイベント時にスクリプトを実行できることがわかりました。

Hooks reference - Claude Docs

関連するイベントは以下の通りです。

  • PermissionRequest
    • Claudeが承認ダイアログを表示した時
  • Stop
    • Claudeが応答完了した時
  • Notification
    • Claudeが通知を送信した時

というわけで、今回はPermissionRequestを使ってみます。

hookに渡される情報

PermissionRequest時、JSON形式でstdinに情報が渡されます。

Hook inputs - Claude Docs

tool name

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "git commit -m \"...\""
  }
}

このtool_nametool_inputを使えば、どのツールで何をしようとしているのかを通知に表示できそうです。

hook設定

~/.claude/settings.json を編集します。

{
  "hooks": {
    "PermissionRequest": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify-permission.sh"
          }
        ]
      }
    ]
  }
}

$CLAUDE_PROJECT_DIR はClaude Codeがhook実行時に提供する環境変数で、プロジェクトルート(Claude Codeを起動したディレクトリ)のパスに展開されます。

ℹ️ Note

例えば /Users/yourname/projects/my-app で Claude Code を起動していれば、"$CLAUDE_PROJECT_DIR"/.claude/hooks/notify-permission.sh/Users/yourname/projects/my-app/.claude/hooks/notify-permission.sh に展開されます。絶対パスをハードコードしなくて済むので便利です。
Environment variables - Claude Docs

スクリプトファイルの作成

設定ファイルで指定したパスにスクリプトを作成します。
指定したパスはプロジェクト直下の.claude/hooks/を指します。

# ディレクトリを作成
mkdir -p .claude/hooks

# スクリプトファイルを作成
touch .claude/hooks/notify-permission.sh

# 実行権限を付与
chmod +x .claude/hooks/notify-permission.sh

以降、作成したファイル(notify-permission.sh)に処理を書いていきます。以下で紹介する通知方法のコードを参考にしてください。


通知方法いろいろ

macOSで通知を出す方法はいくつかあります。とりあえず、順番に試してみました。 osascriptを使う方法と、外部ツールを使う方法の2種類です。

以降で紹介するコードは、すべて notify-permission.sh に書く内容です。

osascript とは

osascript はmacOSに標準で搭載されているコマンドラインツールで、AppleScriptやその他のOSA(Open Scripting Architecture)言語スクリプトを実行できます。

Scripting with AppleScript - Apple Developer

ターミナルから -e オプションでスクリプトを直接実行できるので、シェルスクリプトからmacOSのGUI機能(通知、ダイアログ、アプリケーション操作など)を呼び出すのに便利です。

osascript -e 'display notification "Hello" with title "Test"'

(これをターミナルで実行すると、通知が表示されます。便利だ。)

まずは、このosascriptを使って通知を出していきます。

1. display notification(バナー通知)

osascriptdisplay notificationを使う方法です。数秒で自動的に消えて、通知センターに履歴が残ります。

基本形

#!/bin/bash

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

osascript -e "display notification \"${TOOL_NAME} の承認を待っています\" with title \"Claude Code\""

音を鳴らす

osascript -e "display notification \"${TOOL_NAME} の承認を待っています\" with title \"Claude Code\" sound name \"Glass\""

sound name で指定したサウンドが鳴らない場合は afplay を使う方法もあります。

afplay /System/Library/Sounds/Glass.aiff &
osascript -e "display notification \"${TOOL_NAME} の承認を待っています\" with title \"Claude Code\""

subtitle で詳細を表示

subtitle オプションを使うと、より詳細な情報を表示できます。

こんなイメージです。

タイトル: Claude Code
サブタイトル: Bash の承認を待っています
本文: cd ~/shimabox && git status

最終形

#!/bin/bash

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [ -n "$COMMAND" ]; then
  DETAIL="$COMMAND"
elif [ -n "$FILE_PATH" ]; then
  DETAIL="$FILE_PATH"
else
  DETAIL=""
fi

osascript -e "display notification \"${DETAIL}\" with title \"Claude Code\" subtitle \"${TOOL_NAME} の承認を待っています\" sound name \"Glass\""

こんな感じで表示されます。
display notification

通知センターにも履歴が残ります。
display notification with subtitle

特徴

  • 数秒で消える
  • 通知センターに残る
  • カスタムアイコン
    • 不可display notification にはアイコン指定オプションがない)

2. display alert(アラート通知)

osascriptdisplay alertを使う方法です。クリックするまで消えません。

display alert

基本形

#!/bin/bash

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

osascript -e "display alert \"Claude Code\" message \"${TOOL_NAME} の承認を待っています\""

詳細メッセージ

display alert には subtitle がないので、message に改行で詳細を含めます。

#!/bin/bash

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [ -n "$COMMAND" ]; then
  DETAIL="$COMMAND"
elif [ -n "$FILE_PATH" ]; then
  DETAIL="$FILE_PATH"
else
  DETAIL=""
fi

osascript -e "display alert \"Claude Code\" message \"${TOOL_NAME} の承認を待っています\n${DETAIL}\" as critical"

音を鳴らす

display alert には音のオプションがないので、afplay を併用します。

afplay /System/Library/Sounds/Glass.aiff &
osascript -e "display alert \"Claude Code\" message \"${TOOL_NAME} の承認を待っています\""

特徴

  • クリックするまで消えない
  • as critical / as warning / as informational で種類を変えられる
  • 音を鳴らすにはafplayで対応可能
  • カスタムアイコンは不可

as warningはmacOSのバージョンによって表示されない場合があります。as critical は確実に警告アイコンが表示されます。


3. display dialog(ダイアログ通知)

osascriptdisplay dialogを使う方法です。こちらもクリックするまで消えませんが、カスタムアイコンが指定できます。

display dialog

基本形

#!/bin/bash

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

osascript -e "display dialog \"${TOOL_NAME} の承認を待っています\" with title \"Claude Code\" with icon caution"

プリセットアイコン

3種類から選べます。

  • note - 吹き出し(情報)
  • caution - 黄色の警告
  • stop - 赤の停止

カスタムアイコン

任意の画像ファイルを指定できます。

osascript -e "set iconPath to POSIX file \"/path/to/icon.png\"" -e "display dialog \"${TOOL_NAME} の承認を待っています\" with title \"Claude Code\" with icon file iconPath"
ℹ️ Note

/path/to/icon.png は、"$CLAUDE_PROJECT_DIR/.claude/hooks/icon.png" のようにプロジェクト内のパスを指定するとよいです。

ボタンのカスタマイズ

デフォルトでは「OK」「キャンセル」の2つのボタンが表示されますが、buttons オプションで変更できます。

# OKボタンだけにする
osascript -e "display dialog \"承認を待っています\" with title \"Claude Code\" buttons {\"OK\"} default button \"OK\""

音を鳴らす

display dialog にも音のオプションがないので、afplay を併用します。

afplay /System/Library/Sounds/Glass.aiff &
osascript -e "display dialog \"${TOOL_NAME} の承認を待っています\" with title \"Claude Code\" with icon caution"

最終形

#!/bin/bash

# stdinからJSONを読み取る
INPUT=$(cat)

# tool_nameを取得
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# tool_inputから詳細を取得
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 詳細メッセージを組み立て
if [ -n "$COMMAND" ]; then
  DETAIL="$COMMAND"
elif [ -n "$FILE_PATH" ]; then
  DETAIL="$FILE_PATH"
else
  DETAIL=""
fi

# 音を鳴らしてダイアログを表示(OKボタンのみ、カスタムアイコン)
afplay /System/Library/Sounds/Glass.aiff &
osascript -e "set iconPath to POSIX file \"/path/to/icon.png\"" \
-e "display dialog \"${TOOL_NAME} の承認を待っています\n${DETAIL}\" with title \"Claude Code\" with icon file iconPath buttons {\"OK\"} default button \"OK\""

特徴

  • クリックするまで消えない
  • カスタムアイコン
    • 可能
    • これはちょっとうれしいかも
  • ボタンのカスタマイズ
    • 可能
    • afplayで対応可能

4. terminal-notifier(外部ツール)

terminal-notifier を使う方法です。カスタムアイコン、クリックアクション、タイムアウトなど柔軟な設定が可能です。

terminal-notifier とは

macOSの通知センターにコマンドラインから通知を送るためのオープンソースツールです。

広く使われており、通知を送るだけのシンプルなツールなので安心して使えるかと思います。

インストール

brew install terminal-notifier

基本形

#!/bin/bash

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

terminal-notifier -title "Claude Code" -message "${TOOL_NAME} の承認を待っています" -sound Glass

クリックでターミナルをアクティブに

通知をクリックすると、Claude Codeが動いているターミナルを前面に持ってきて、すぐに承認操作ができます。これが地味に便利。

terminal-notifier -title "Claude Code" -message "${TOOL_NAME} の承認を待っています" -execute "osascript -e 'tell application \"Ghostty\" to activate'"

ターミナルは人それぞれですから、以下のような感じでターミナルアプリに応じて変更してください。

ターミナル コマンド
Ghostty osascript -e 'tell application "Ghostty" to activate'
iTerm osascript -e 'tell application "iTerm" to activate'
Terminal.app osascript -e 'tell application "Terminal" to activate'
⚠️ Warning

すべてを試したわけではないです!

詳細情報も表示

-subtitle オプションを使うと、より詳細な通知が可能です。

タイトル: Claude Code
サブタイトル: Bash の承認を待っています
メッセージ: cd ~/shimabox && git commit -m "test"

最終形

#!/bin/bash

# stdinからJSONを読み取る
INPUT=$(cat)

# tool_nameを取得
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')

# tool_inputから詳細を取得
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# 詳細メッセージを組み立て
if [ -n "$COMMAND" ]; then
  DETAIL="$COMMAND"
elif [ -n "$FILE_PATH" ]; then
  DETAIL="$FILE_PATH"
else
  DETAIL=""
fi

# terminal-notifier で通知(クリックでターミナルをアクティブに)
terminal-notifier -title "Claude Code" -subtitle "${TOOL_NAME} の承認を待っています" -message "$DETAIL" -sound Glass -contentImage "/path/to/icon.png" -execute "osascript -e 'tell application \"Ghostty\" to activate'"
ℹ️ Note

初回実行時に通知許可を求められます。システム設定 → 通知 → terminal-notifier で許可が必要です。

terminal-notifier setting

osascript系との比較

機能 osascript系 terminal-notifier
カスタムアイコン dialog のみ
○(notification は標準、他はafplayと併用する)
クリック時にURL開く × ○ (-open)
クリック時にコマンド実行 × ○ (-execute)
タイムアウト設定 × ○ (-timeout)
通知センターに残る notification のみ

使えるサウンド一覧

/System/Library/Sounds/ にあるファイルが使えます。

  • Glass, Ping, Pop, Purr, Submarine
  • Blow, Bottle, Frog, Funk, Hero
  • Morse, Sosumi, Tink, Basso

あと、自分で好きな音も選べるかと思います。

通知方法の比較まとめ

方法 消えない カスタムアイコン モーダル
display notification × × ×
display alert △(afplay) ×
display dialog △(afplay)
terminal-notifier ×

個人的には、display dialog も使えるかなと思いましたが、terminal-notifier が一番使い勝手がよいかなと思っています。通知をクリックしてターミナルに戻れたりするのがよいです。

あと、細かい設定もしやすいのかなと思います。作業中で通知がわずらしい場合は通知をそもそもオフにしたりできますし。

terminal-notifier setting

断念したこと 😢

Automatorアプリや AppleScript アプリを作成してカスタムアイコンでの通知を出す方法も試したのですが、hookからアプリに引数(tool_name)を渡す方法がうまくいかず断念しました。

display dialogwith icon file または terminal-notifier-contentImage でカスタムアイコンを指定する方が簡単でした。


おわりに

Claude Codeのhooks機能を使って、承認待ちの通知をmacOSに飛ばす方法を試してみました。

個人的にはterminal-notifier, terminal-notifier + クリックでターミナルアクティブ化 の組み合わせがけっこう良いかと思っています(色々とカスタムできそうですし)。これで安心して別の作業ができる。めでたしめでたし。

他にもいいやり方はもちろんあると思いますが、まず自分が試してみた結果をまとめてみました。
自分で手を動かすのはやっぱ大事だよねって。そこで、他にいい方法があれば試せばいいのだし。
(自分で考えた?設定は愛着が湧くし??)

というわけで、他にいい方法があればぜひ教えてください。

それでは。

スポンサーリンク