Claude Media
Claude CodeをWezTermで使うときの通知設定

Claude CodeをWezTermで使うときの通知設定

WezTermはClaude Codeのデスクトップ通知が既定の対象に入っていません。ベルとOSC 9のNotification hookで受け取る方法をまとめます。

このTipsでできること

WezTermはClaude Codeのデスクトップ通知が既定の通知対象に入っていないターミナルです。ベル文字での気づき方と、OSC 9を使ったNotification hookでの文言付き通知、WezTerm側の表示条件の絞り込みまでを扱います。

WezTermは通知の対応リストに入っていない

Claude Codeは応答の完了後しばらく入力がないときや、権限確認の応答待ちのまま離席したと判断したときにNotificationイベントを発火します。これを画面の外まで届けるデスクトップ通知は、既定ではGhostty・Kitty・iTerm2の3つでしか動きません。WezTermはこの3つに含まれておらず、preferredNotifChannelが既定値の"auto"のままだと何も起こりません。

"auto"の挙動は設定リファレンスに書かれています。Terminal.appではターミナル側の音を切っているときだけベルを鳴らし、それ以外のターミナルでは何もしません。WezTermにはこのベルのフォールバックも当たらないため、無音のまま放置されます。

バグでも設定漏れでもなく、対応リストの仕様です。長時間のリファクタリングやテスト実行を任せて離席する運用ほど、この差が効いてきます。

通知が鳴るのは離席したと判断されてから

permission_promptとidle_promptは、画面を見ていると判断されている間は発火しません。権限確認と放置では待ち時間が違います。

あゆみ

通知が発火するまでの待ち時間

  1. 0秒権限確認の画面が出る

    この時点ではまだ何も発火しません。ここからタイマーが動き始めます。

  2. 約6秒後permission_prompt

    その間にキーを押していなければ発火します。キー入力のたびに待ち時間は後ろへ延びます。

  3. 応答の完了から約60秒後idle_prompt

    完了後に入力がなく、バックグラウンドのサブエージェントも動いていないときに発火します。

ベルもNotification hookも、このタイミングを共有します。完了した瞬間に鳴るわけではないので、「終わったのに鳴らない」と感じたら、まず60秒待ってみてください。

ベル文字でまず気づけるようにする

最も手早い対処はpreferredNotifChannelを"terminal_bell"に切り替えることです。

{
  "preferredNotifChannel": "terminal_bell"
}

この設定にすると、Claude CodeはターミナルにASCIIのBEL文字を送るようになります。WezTermはBELを受け取るとaudible_bellの設定に従ってベルを鳴らします。既定値は"SystemBeep"で、追加設定なしにシステムの通知音が鳴ります。Waylandにはシステムビープの仕組みがないため、Waylandでは音が出ません。音を止めたいときは"Disabled"を指定します。

音の代わりに画面を光らせたい場合は、visual_bellが使えます。ペインの背景色(既定)かカーソル色が一瞬変わる設定です。

local wezterm = require 'wezterm'
local config = wezterm.config_builder()
 
config.audible_bell = 'Disabled'
config.visual_bell = {
  fade_in_duration_ms = 75,
  fade_out_duration_ms = 75,
  target = 'CursorColor',
}
 
return config

fade_in_duration_msとfade_out_duration_msの既定値はどちらも0です。2つの合計が0のままだと、視覚的な合図は一切出ません。audible_bellを"Disabled"にしたうえで時間を指定し忘れると、音も光も出ない状態になります。

ここまでは音や色で気づく手段で、通知センターに文言付きのカードは出ません。離席中に本文まで確認したいなら、次のNotification hookが要ります。

OSC 9でデスクトップ通知を出す

Claude CodeのNotification hookは、JSON出力にterminalSequenceというフィールドを含められます。制御端末を持たないhookプロセスの代わりに、Claude Code自身がエスケープシーケンスを書き出す仕組みです。このフィールドはv2.1.141で追加されました。

許可されるシーケンスはOSCの0・1・2・9・99・777とBELに限られます。OSC 9はiTerm2・ConEmu・Windows Terminal・WezTermの通知として扱われます。OSC 777はurxvt・Ghostty・Warp向けで、WezTermはこの一覧に入っていません。許可リスト外の列が1つでも混ざると、フィールドごと無視されます。

WezTerm自体はOSC 777の通知(titleとbodyを持つ形式)も受け付けます。それでもterminalSequenceから出す形式はOSC 9に揃えておくと迷いません。OSC 9が運べるのは本文だけで、タイトルは付きません。

次のhookは、Notificationイベントが発火するたびにOSC 9で通知の本文(message)を渡します。

{
  "hooks": {
    "Notification": [
      {
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/wezterm-notify.sh" }]
      }
    ]
  }
}
#!/bin/bash
input=$(cat)
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]9;%s\007' "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

スクリプト単体なら、Claude Codeを起動せずに動作を確かめられます。Notification hookが受け取るJSONをechoで流し込むだけです。

echo '{"message":"Claude needs your permission to use Bash","notification_type":"permission_prompt"}' | ~/.claude/hooks/wezterm-notify.sh

出力は次のJSON1行になります(jqで組み立てるので、制御文字が\u001bと\u0007にエスケープされます)。

{"terminalSequence":"\u001b]9;Claude needs your permission to use Bash\u0007"}

messageが含まれていないときは、スクリプトの//以降の既定文言が使われます。このスクリプトはjqがあれば単体で動き、上のJSONを返します。

terminalSequenceは、インタラクティブセッションの画面表示中にしか書き出されません。-pのヘッドレスモードやAgent SDK経由では無視されます。

Notification hookへの入力には、message本文のほかにnotification_typeと、あればtitleも渡されます。matcherで絞り込めば、種類ごとに別の通知を出し分けられます。権限確認のときだけWezTermへ通知したい場合は次のようにします。

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/wezterm-notify.sh" }]
      }
    ]
  }
}

matcherを省略すると、これまでの例のようにすべての種類で発火します。notification_typeにはpermission_prompt・idle_promptのほかに、認証完了(auth_success)やバックグラウンドセッションの完了(agent_completed)などがあります。

Notification hookは、preferredNotifChannelを"notifications_disabled"にしていても実行されます。この設定が変えるのは通知の出し方で、hookが動くかどうかではありません。

動作確認だけなら、hookを介さずターミナルへ直接エスケープシーケンスを流し込んでも同じ通知が出ます。

printf '\033]9;%s\007' "Claude Code test notification"

WezTermの画面にトースト通知が出れば、hookの経路も同じ形式で届きます。

WezTerm側で表示条件を絞り込む

WezTermには受け取った通知をどう表示するかを制御するnotification_handlingという設定キーがあります。バージョン20240127-113634-bbcac864以降が対象です。既定値は"AlwaysShow"で、フォーカス状態に関係なく常に表示します。

local wezterm = require 'wezterm'
local config = wezterm.config_builder()
 
config.notification_handling = 'SuppressFromFocusedWindow'
 
return config

応用: bellイベントだけでトースト通知を出す

Notification hookを書かなくても、WezTermのbellイベントを使えば似た体験に近づけられます。preferredNotifChannelを"terminal_bell"にした状態で、~/.wezterm.luaに次のハンドラを足します。ベルが鳴るたびに、WezTerm自身がトースト通知を出します。

local wezterm = require 'wezterm'
local config = wezterm.config_builder()
 
wezterm.on('bell', function(window, pane)
  window:toast_notification('Claude Code', 'Needs your attention', nil, 4000)
end)
 
return config

toast_notificationの第4引数はミリ秒の表示時間ですが、OSによっては守られません。X11やWaylandでは指定どおりにならないことがあり、Windowsは固定の長さで表示します。

この方法には見落としやすい制約があります。bellイベントが受け取るのはBELが鳴った事実だけで、権限確認待ちなのか放置なのかを区別する本文は含まれません。表示できるのは固定文言だけです。

もう1つ、bellイベントのハンドラはWezTermのベル処理を置き換えません。上乗せで動くので、audible_bellが"SystemBeep"のままなら、音とトーストが両方出ます。

3つの手段の使い分け

使い分け

どれを選ぶか

  • terminal_bellのみ

    Claude Code側の設定1行で済みます。音か画面の色変化で、まず気づければ十分な場合向きです。

  • Notification hook + OSC 9

    hookスクリプト1本が要ります。通知の本文まで見てから戻りたい場合向きです。

  • bellイベント + toast_notification

    ~/.wezterm.luaへの追記だけで済みます。固定文言で足りて、hookを増やしたくない場合向きです。

3つは排他ではありません。terminal_bellとNotification hookは別々に動くので、両方有効にして、片方が届かない環境の保険にできます。

よくあるつまずき

tmuxの中でWezTermを使っている場合は、~/.tmux.confにallow-passthroughを設定していないと、デスクトップ通知がtmuxに止められて外側のWezTermまで届きません。tmux固有の設定手順はtmux配下でのClaude Code設定にまとめています。

SSH越しの通知について、Claude Codeのdocsが説明しているのはGhostty・Kitty・iTerm2の通知です。WezTermでは、まず同一マシン上で通知が出ることを確かめてから、リモート接続を試すと原因を切り分けやすくなります。

通知が出ないときは、次の順に確かめます。

手順

通知が出ないときの切り分け

  1. 1

    WezTermのバージョンを見る

    wezterm --versionで、notification_handlingの対応バージョン(20240127-113634-bbcac864以降)を満たしているかを確かめます。

  2. 2

    Claude Codeの設定を見る

    /configのLocal notificationsが、意図した値(terminal_bellなど)になっているかを見ます。

  3. 3

    printfで直接試す

    先ほどのprintfが通らなければ、WezTermかOSの通知許可が原因です。OS側の設定で、WezTermに通知が許可されているかを確かめます。

  4. 4

    hookスクリプトを見る

    printfは通るのにhook経由だけ届かないときは、スクリプトに実行権限(chmod +x)があるか、jqが入っているかを確かめます。標準出力に余計な文字列が混ざると、JSONとして読めず、terminalSequenceごと無視されます。

Notification hook自体の書き方や、他のイベントと組み合わせた実装パターンはClaude Code Hooks実例カタログにまとまっています。preferredNotifChannelの全体像や、対応ターミナルごとの違いはClaude Code通知の設定を参照してください。

まとめ

通知の経路が増えるほど、止まっている層は見つけにくくなります。まず素のWezTermでprintfの通知を出し、そのあとでtmuxやSSHを挟むと、どこで止まるのかを切り分けられます。

この記事を共有:XはてブLinkedIn