Claude Code通知の設定 — ターミナルベルとNotificationフック
Claude Codeの完了通知が届かないときの原因と、preferredNotifChannel・Notification hook・iTerm2設定による直し方をまとめます。
通知が来ない原因は、ターミナルと発火条件の2つで決まる
Claude Codeは、タスクが終わったときや権限確認で応答を待っているときに、Notificationイベントを発火します。ただし発火するのは、ユーザーがターミナルから離れていると判断したときです。キーを打っている間は鳴りません。
もう一つの分かれ目がターミナルの種類です。デスクトップ通知が既定で届くのはGhostty・Kitty・iTerm2の3つだけです。Warpのような他のターミナルでは、何も設定しないと画面の外へ何も届きません。大きめのリファクタリングを投げて離席したときや、Bashの実行許可を求められたまま放置したときに、この差が出ます。
この記事は症状から引ける順に並べています。まず発火のタイミング、次にpreferredNotifChannelとiTerm2・tmuxの直し方、そのあとNotification hookとスマホへのPush通知を扱います。
Notificationはいつ発火するのか
発火のタイミングは通知の種類ごとに違い、hookのmatcherで種類を選べます。「鳴るまでに時間がかかる」と感じる場合の多くは、この待ち時間が理由です。
よく使う通知の種類と待ち時間
permission_prompt
ツールの使用や、サンドボックス内コマンドのネットワークリクエストの承認待ちです(後者のターミナルでの通知はv2.1.246以降)。プロンプトが約6秒待たされてから発火します。ターミナルではキー入力のたびに後ろへずれます。
idle_prompt
Claudeの応答が終わってから約60秒後、その間に何も入力していないときに発火します。claude.aiの使用量上限のリセット待ちの間は送られません。
elicitation_dialog
MCPサーバーが入力フォームを開き、約6秒入力がないときに発火します。ブラウザでURLを開くよう求める
elicitation_url_dialogも同じ待ち時間です。
上の3つのほかにauth_successがあります。バックグラウンドセッション向けのagent_needs_inputとagent_completedはv2.1.198以降です。使用量上限の自動再開に関するquota_auto_resume_firedなどはv2.1.234以降です。タスク完了の通知を待っているのに来ないときは、応答直後ではなくidle_promptの60秒を待っている可能性があります。
権限確認を待たずに即座にhookを走らせたい場合は、NotificationではなくPermissionRequestイベントを使います。
既定のままだと何が起きるか
preferredNotifChannelは~/.claude/settings.jsonに書く設定で、既定値は"auto"です。"auto"ではiTerm2・Ghostty・Kittyにデスクトップ通知を送り、Terminal.appでは音の出るベルが切ってあるときだけベルを鳴らします。それ以外のターミナルでは何もしません。
この既定を変えるには、"terminal_bell"を指定します。
{
"preferredNotifChannel": "terminal_bell"
}terminal_bellにすると、どのターミナルでもベル文字が鳴ります。値は他に"iterm2"・"iterm2_with_bell"・"kitty"・"ghostty"・"notifications_disabled"があります。/config画面ではLocal notificationsという項目で同じ設定を変えられます。
デスクトップ通知はSSH経由でも手元のマシンまで届きます。GhosttyとKittyは追加設定なしでOSの通知センターへ転送しますが、iTerm2だけは転送を有効にする作業が要ります。
iTerm2でデスクトップ通知を有効にする
iTerm2の設定手順
- 1
プロファイル設定を開く
Settings → Profiles → Terminalを開きます。
- 2
Notification Center Alertsを有効にする
「Notification Center Alerts」にチェックを入れます。
- 3
エスケープシーケンス由来の通知を許可する
「Filter Alerts」をクリックし、「Send escape sequence-generated alerts」を有効にします。
この2箇所が有効でないと、iTerm2は通知のエスケープシーケンスを受け取っても画面外へ転送しません。設定してもまだ来ないなら、iTerm2そのものではなくOS側の通知権限を疑います。ターミナルアプリにOSの通知権限がなければ、iTerm2の設定が正しくても表示されないためです。
tmuxの中では追加設定が要る
tmuxの中でClaude Codeを動かしている場合、allow-passthroughを有効にしないと、通知はtmuxに飲み込まれて外側のターミナルまで届きません。手順は~/.tmux.confへの追記とtmux source-fileによる反映です。フルスクリーンレンダリングとの併用も含めた設定はClaude Code tmux設定にあります。パススルーが通っていれば、terminal_bellへの切り替えもそのまま使えます。
Notification hookで音やカスタム処理を実行する
preferredNotifChannelが対応していないターミナルでも、Notification hookなら任意のコマンドを実行できます。組み込みの通知の代わりではなく並行して動くので、既存の通知設定を変えずに音だけ足すことも可能です。notifications_disabledにしていてもhookは実行されます。
{
"hooks": {
"Notification": [
{
"hooks": [{ "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff" }]
}
]
}
}上の例はmacOSでシステムサウンドを鳴らします。Linuxならnotify-send、Windowsならpowershell.exe経由の標準機能で、同じNotificationイベントに紐づけられます。Slackへの投稿のように、コマンドとして実行できるものは何でも組み込めます。Notification以外のイベントも含めたレシピはClaude Code Hooks実例カタログにあります。
matcherで通知の種類ごとに処理を分ける
matcherを省くと全種類の通知でhookが走ります。権限確認だけを別の音にしたい場合は、permission_promptとidle_promptのように分けて登録します。hookの標準入力には、通知の文面を持つmessage、任意のtitle、種類を示すnotification_typeのJSONが渡ります。
macOSでosascriptのdisplay notificationを使うと、コマンドが黙って失敗することがあります。通知がScript Editorアプリ経由で出る仕組みで、Script Editorに通知権限がないと失敗し、権限の確認画面も出ません。一度osascript -e 'display notification "test"'を実行してから、システム設定の通知でScript Editorを許可します。Linuxのnotify-sendは、デスクトップ通知デーモンのないヘッドレスサーバーやSSH先、多くのコンテナでは動きません。
対応外のターミナルにデスクトップ通知を出す
Notification hookの出力JSONにterminalSequenceフィールドを入れると、Claude Code自身がエスケープシーケンスを端末へ書き出します。hookはコントロール端末を持たないので、/dev/ttyへ直接書く方法は失敗します。この方法ならtmuxやGNU screenの中でも動きます。
許可されているのは次の種類だけです。タイトル用のOSC 0・1・2、OSC 9(iTerm2・ConEmu・WezTerm・Windows Terminal)、OSC 99(Kitty)、OSC 777(urxvt・Ghostty・Warp)、BELです。許可外のシーケンスを含むとフィールド全体が無視されます。Warpのように既定ではデスクトップ通知が来ないターミナルでも、公式はOSC 777をWarpの通知として挙げており、Warpでも通知を出せます(この記事では実機で確認していません)。
#!/bin/bash
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'このスクリプトを単体で動かし、通知のJSONを標準入力に渡した結果が次のとおりです。手元のClaude Code v2.1.285とjqで確認しました。実際のClaude Codeに通知を出させた確認ではなく、スクリプトが返すJSONの確認です。
echo '{"message":"Claude needs your permission","notification_type":"permission_prompt"}' | ./notify.sh
{"terminalSequence":"\u001b]777;notify;Claude Code;Claude needs your permission\u0007"}
echo '{}' | ./notify.sh
{"terminalSequence":"\u001b]777;notify;Claude Code;Needs your attention\u0007"}messageが無い入力でも、jqの//が既定の文面に切り替えるため壊れません。この方式が動くのは対話セッションで画面が表示されているときだけです。-pによる非対話実行とAgent SDKでは、フィールドは無視されます。WezTermのOSC 9を使う設定はWezTermでClaude Codeの通知を受け取る設定にあります。
スマホへのPush通知は別の仕組み
ここまでの設定は、Claude Codeを動かしているマシンに向けた通知です。スマートフォンへのPush通知は別の設定です。agentPushNotifEnabledとinputNeededNotifEnabledで有効にし、Remote Controlの接続中にClaudeモバイルアプリへ送られます。仕組みが別なので、ターミナル側の通知とは同時に有効にできます。
手元の通知とスマホのPush通知
ターミナル・デスクトップ通知
preferredNotifChannelとNotification hookで設定します。Remote Controlは要りません。離席しているときに鳴ります。
モバイルPush通知
/configのPush when Claude decidesとPush when actions requiredで設定します。Remote Controlの接続が前提で、既定はどちらもオフです。
設定の流れは、モバイルアプリを入れて同じアカウントでサインインし、OSの通知許可を承認し、ターミナルの/configで2つのどちらかまたは両方をオンにする、というものです。前者はClaudeが送る価値があると判断したとき、たとえば長いタスクの完了時に送られます。後者は権限確認や質問が待っているときに送られます。
来ないときの切り分けは3つです。/configに「No mobile registered」と出ていれば、スマホでClaudeアプリを開いてプッシュトークンを更新します。次回のRemote Control接続で警告は消えます。iOSでは、集中モードや通知の要約が遅らせたり抑えたりするので、設定 → 通知 → Claudeを確認します。Androidでは、バッテリー最適化が配信を遅らせることがあるため、Claudeアプリを対象外にします。
接続元のターミナルを入力中またはフォーカスしているあいだは、モバイルPushはスキップされます。手元で作業していてPushが来ないのは仕様どおりです。この判定を外す環境変数は、対象がPushNotificationツールのデスクトップ通知で、モバイルPushはサーバー側の判断で抑えられることがあります。詳細はCLAUDE_CODE_DISABLE_NOTIFICATION_PRESENCE_CHECKで在席中でも通知を送る設定にあります。
通知チャンネルの使い分け
preferredNotifChannelの値 | 挙動 | 向いている場面 |
|---|---|---|
auto(既定) | 挙動iTerm2・Ghostty・Kittyでデスクトップ通知。Terminal.appは音の出るベルがオフのときだけベル | 向いている場面対応ターミナルを常用している |
terminal_bell | 挙動全ターミナルでベル文字を鳴らす | 向いている場面Warp・VS Code統合ターミナルなど |
iterm2 / iterm2_with_bell | 挙動iTerm2のデスクトップ通知(後者はベルも) | 向いている場面iTerm2で挙動を固定したい |
notifications_disabled | 挙動組み込みの通知を出さない。Notification hookは動く | 向いている場面通知を止めたい |
対応ターミナルを常用しているならautoのままで足ります。非対応ターミナルや遠隔作業が多いならterminal_bellを基本にし、見逃したくない合図だけNotification hookで別経路にも流す組み合わせが使えます。社給端末で通知許可が絞られている場合や、通知センターを普段見ない運用では、hookからSlackなどへ直接送る方が見逃しにくくなります。
よくある質問
WarpやVS Code統合ターミナルで、デスクトップ通知は出せないのか?
組み込みのautoでは出ません。terminal_bellでベルを鳴らすか、Notification hookを使います。WarpはterminalSequenceのOSC 777が許可されているので、hookからOSの通知を出す方法もあります。
スクリーンリーダーモードでは、設定が要るのか?
スクリーンリーダーモードでは、Claudeが注意を求めるときに既定でターミナルベルが鳴ります。terminal_bellの指定は不要です。この設定はモードの外で同様のベルを得るためのものです。読み上げ関連はClaude Codeスクリーンリーダー対応ガイドにまとめています。
Notification hookで通知そのものを止めたり書き換えたりできるのか?
できません。Notification hookは通知をブロックも変更もできず、副作用の実行に使うものです。systemMessageとcontinueは破棄され、terminalSequenceだけが処理されます。
モバイルPush通知を、タスク完了だけに絞れるのか?
絞れません。Push when Claude decidesとPush when actions requiredの2つのオン・オフ以外に、イベント単位の設定はありません。プロンプトに「テストが終わったら通知して」と書いて、Pushを依頼する方法はあります。
まとめ
通知が来ないときは、まず自分のターミナルが既定の3つに入っているかを見ます。入っていなければterminal_bellかNotification hook、iTerm2ならOS側の権限とプロファイル設定、tmuxならパススルーが確認先です。離席後すぐ鳴らないのは、6秒や60秒の待ち時間によるもので、故障ではありません。外出先で気づきたいならモバイルPushを別に設定します。