VOOZH about

URL: https://zenn.dev/milmed/articles/845f47259eca07

⇱ Claude Code 「俺のターン」を確実に検知する


Claude Code
tech

👁 Image

👁 Image

はじめに

Claude Codeで長時間の処理を実行していると、完了時の通知ベルに気づかないことはありませんか?
特に他の作業をしながら待っている時は、処理が終わっていることに気づくのが遅れてしまいがちです。

この記事では、Claude Codeのhooks機能を使って以下の2つのアプローチで「俺のターン」を見逃さない方法を紹介します。

  1. 通知音 — macOSの通知センターで処理完了を音で知る
  2. tmuxタブのビジュアル表示 — 処理中はスピナー、完了後は未読マーカーで状態を可視化する

Claude Code hooks とは

Claude Codeには、特定のイベント発生時にシェルコマンドを実行できるhooks機能があります。

https://docs.anthropic.com/en/docs/claude-code/hooks

本記事では以下の2つのイベントを使用します。

イベント 発火タイミング
UserPromptSubmit ユーザーがプロンプトを送信した時
Stop Claudeが応答を返し終わった時

設定は /hooks コマンドから対話的に行うか、~/.claude/settings.json を直接編集します。

1. 通知音で検知する

最もシンプルな方法です。Stop イベントにmacOSの通知コマンドを設定します。

設定

~/.claude/settings.json

{
 "hooks": {
 "Stop": [
 {
 "matcher": "",
 "hooks": [
 {
 "type": "command",
 "command": "osascript -e 'display notification \"確認してください\" with title \"Claude Code\" sound name \"Glass\"'"
 }
 ]
 }
 ]
 }
}

通知方法のバリエーション

macOSの通知センターを使用(推奨)

通知音と共に通知センターに表示します。最もシンプルで実用的な方法です。

osascript -e 'display notification \"完了しました\" with title \"Claude Code\" sound name \"Glass\"'

カスタム音楽ファイルで通知

お気に入りの音楽ファイルを再生して通知します。理想のClaude Code体験を実現できます。

afplay ~/Music/end_of_turn.mp3

音声読み上げで通知

macOSの音声読み上げ機能を使用して、処理完了を音声で知らせます。

say '完了しました'

2. tmuxタブで視覚的に検知する

通知音だけでは、複数のClaude Codeセッションを同時に動かしている場合にどのウィンドウが完了したのか分かりにくいことがあります。

tmux上でClaude Codeを実行している場合、タブの表示を動的に変えることで処理状態を一目で把握できます。

状態遷移

👁 Image

状態 タブ表示 説明
処理中 ⠋ node スピナーがアニメーションで回転
応答完了(未読) ● node 赤い●で未確認の応答があることを通知
確認済み node ウィンドウに切り替えると自動的にクリア

仕組みの全体像

[ユーザーがプロンプト送信]
 ↓ UserPromptSubmit hook
 ↓ tmux-spinner-start.sh
 ↓ タブに ⠋ ⠙ ⠹ ... スピナー表示

[Claude が応答を返す]
 ↓ Stop hook
 ↓ tmux-spinner-stop.sh
 ↓ スピナー停止 → 元のウィンドウ名に復元
 ↓ 別ウィンドウにいる場合 → @unread フラグ設定 → 赤い ● 表示

[ユーザーがウィンドウに切り替え]
 ↓ tmux pane-focus-in hook
 ↓ @unread クリア → ● が消える

設定手順

Step 1: スクリプトの作成

2つのシェルスクリプトを作成します。

~/.claude/scripts/tmux-spinner-start.sh — スピナー開始

#!/usr/bin/env bash
set -euo pipefail

# tmux外では何もしない
[[ -z "${TMUX:-}" ]] && exit 0

# このペーンが属するウィンドウIDを取得
PANE_ID="${TMUX_PANE:-$(tmux display-message -p '#{pane_id}' 2>/dev/null || echo 'default')}"
WINDOW_ID=$(tmux display-message -t "${PANE_ID}" -p '#{window_id}' 2>/dev/null || echo '')
[[ -z "$WINDOW_ID" ]] && exit 0

SUFFIX="${PANE_ID//[^a-zA-Z0-9]/_}"
PID_FILE="/tmp/claude-tmux-spinner-${SUFFIX}.pid"
NAME_FILE="/tmp/claude-tmux-spinner-${SUFFIX}.name"

# 未読フラグをクリア(新しいリクエスト開始)
tmux set-option -w -t "$WINDOW_ID" -u @unread 2>/dev/null || true

# 既存のスピナーを停止
if [[ -f "$PID_FILE" ]]; then
 kill "$(cat "$PID_FILE")" 2>/dev/null || true
 rm -f "$PID_FILE"
fi

# 元のウィンドウ名を保存(まだ保存されていない場合のみ)
if [[ ! -f "$NAME_FILE" ]]; then
 tmux display-message -t "$WINDOW_ID" -p '#{window_name}' > "$NAME_FILE" 2>/dev/null
fi
ORIGINAL_NAME=$(cat "$NAME_FILE" 2>/dev/null || echo 'bash')

# スピナーをバックグラウンドで開始(ウィンドウIDを明示的に指定)
(
 SPINNER=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
 i=0
 while true; do
 tmux rename-window -t "$WINDOW_ID" "${SPINNER[$i]} ${ORIGINAL_NAME}" 2>/dev/null || break
 i=$(( (i + 1) % ${#SPINNER[@]} ))
 sleep 0.1
 done
) > /dev/null 2>&1 &
disown

echo $! > "$PID_FILE"
exit 0

~/.claude/scripts/tmux-spinner-stop.sh — スピナー停止 & 未読フラグ設定

#!/usr/bin/env bash
set -euo pipefail

# tmux外では何もしない
[[ -z "${TMUX:-}" ]] && exit 0

# このペーンが属するウィンドウIDを取得
PANE_ID="${TMUX_PANE:-$(tmux display-message -p '#{pane_id}' 2>/dev/null || echo 'default')}"
WINDOW_ID=$(tmux display-message -t "${PANE_ID}" -p '#{window_id}' 2>/dev/null || echo '')

SUFFIX="${PANE_ID//[^a-zA-Z0-9]/_}"
PID_FILE="/tmp/claude-tmux-spinner-${SUFFIX}.pid"
NAME_FILE="/tmp/claude-tmux-spinner-${SUFFIX}.name"

# スピナーを停止
if [[ -f "$PID_FILE" ]]; then
 kill "$(cat "$PID_FILE")" 2>/dev/null || true
 rm -f "$PID_FILE"
fi

# 元のウィンドウ名を復元
if [[ -n "$WINDOW_ID" ]]; then
 if [[ -f "$NAME_FILE" ]]; then
 ORIGINAL_NAME=$(cat "$NAME_FILE" 2>/dev/null)
 tmux rename-window -t "$WINDOW_ID" "$ORIGINAL_NAME" 2>/dev/null || true
 rm -f "$NAME_FILE"
 else
 tmux set-option -w -t "$WINDOW_ID" automatic-rename on 2>/dev/null || true
 fi

 # 別のウィンドウにいる場合のみ未読フラグを設定
 CURRENT_WINDOW=$(tmux display-message -p '#{window_id}' 2>/dev/null || echo '')
 if [[ "$CURRENT_WINDOW" != "$WINDOW_ID" ]]; then
 tmux set-option -w -t "$WINDOW_ID" @unread 1 2>/dev/null || true
 fi
fi
exit 0

作成したら実行権限を付与します。

chmod +x ~/.claude/scripts/tmux-spinner-start.sh
chmod +x ~/.claude/scripts/tmux-spinner-stop.sh

Step 2: Claude Code hooks の設定

~/.claude/settings.jsonUserPromptSubmitStop のフックを設定します。
通知音と組み合わせた設定例です。

{
 "hooks": {
 "UserPromptSubmit": [
 {
 "matcher": "",
 "hooks": [
 {
 "type": "command",
 "command": "~/.claude/scripts/tmux-spinner-start.sh"
 }
 ]
 }
 ],
 "Stop": [
 {
 "matcher": "",
 "hooks": [
 {
 "type": "command",
 "command": "osascript -e 'display notification \"確認してください\" with title \"Claude Code\" sound name \"Glass\"'"
 },
 {
 "type": "command",
 "command": "~/.claude/scripts/tmux-spinner-stop.sh"
 }
 ]
 }
 ]
 }
}

Step 3: tmux の設定

.tmux.conf に未読インジケーターの表示設定を追加します。

# 非アクティブウィンドウ: @unread フラグがある場合は赤い●を表示
setw -g window-status-format '#{?@unread,#[fg=colour196]● #[fg=colour250],#[fg=colour250] }#I #W '

# ウィンドウにフォーカスしたら未読フラグを自動クリア
set-hook -g pane-focus-in 'set-option -w -u @unread'

設定をリロードします。

tmux source-file ~/.tmux.conf

ポイント解説

ウィンドウIDの明示指定が重要

tmux rename-window-t "$WINDOW_ID" なしで実行すると、ユーザーが現在見ているアクティブウィンドウが更新されてしまいます。非アクティブタブでもスピナーを正しく動かすには、フック起動時にウィンドウIDを取得し、明示的に指定する必要があります。

複数セッション対応

PIDファイルやウィンドウ名の保存は $TMUX_PANE をキーにしているため、複数のtmuxウィンドウでそれぞれClaude Codeセッションを実行しても干渉しません。

未読フラグの自動クリア

tmuxのカスタムウィンドウオプション @unreadpane-focus-in フックを組み合わせることで、ウィンドウに切り替えた瞬間に赤い●が自動的に消えます。メッセージアプリの未読バッジのような体験です。

3. Agent Deck で一元管理する

複数のClaude Codeセッションを本格的に運用するなら、専用のダッシュボードツール Agent Deck という選択肢もあります。

Agent Deckはtmux上で動作するオープンソースのターミナルセッションマネージャーで、Claude Code・Gemini CLI・Aider・Codexなど複数のAIエージェントを1つの画面から監視・操作できます。

ステータス検知

Agent Deckは各セッションを2秒間隔でポーリングし、状態をリアルタイムに表示します。

状態 表示 説明
Running (緑) エージェントが処理中
Waiting (黄) 応答完了、ユーザー入力待ち
Idle (灰) セッション停止中
Error (赤) セッションがクラッシュ

自前のhooksスクリプトを書かなくても、応答完了が黄色のWaitingステータスとして一覧に表示されます。

主な機能

  • セッション一覧: 複数のClaude Codeセッションやその他のエージェントをまとめて一画面で監視
  • フィルタリング: @ で入力待ちセッションだけを絞り込み、! で処理中のものだけを表示
  • セッションフォーク: f キーで会話を複製し、別アプローチを並行探索(会話履歴を完全に継承)
  • MCPマネージャー: MCP サーバーの追加・削除を設定ファイル編集なしにトグル操作で管理

インストールと起動

# インストール
curl -fsSL https://raw.githubusercontent.com/asheshgoplani/agent-deck/main/install.sh | bash

# セッションの追加
agent-deck add ~/Projects/my-app -t "my-app" -c claude

# ダッシュボードの起動
agent-deck

まとめ

Claude Codeのhooks機能を活用することで、処理完了時の通知を自分好みにカスタマイズできます。

方法 メリット 適するケース
通知音 設定が簡単、他のアプリを見ていても気づく シングルセッション
tmuxビジュアル どのウィンドウが完了したか一目瞭然 マルチセッション
Agent Deck 多数のセッションを本格運用・管理したい マルチセッション

お好みの方法を設定して、快適なClaude Codeライフをお楽しみください!

Discussion