本文へ移動
hanzochang

技術と実践のノート

[並列作業] ClaudeCodeに声でリマインダさせて、待ち時間放置時間をゼロに!

公開 更新

著者:株式会社hanzochang

Claude Codeが完了や質問待ちになったら音声で喋って教えてくれる。放置が続けば繰り返しリマインド、応答したら自動停止。セッション終了後のリマインダ残留問題の対策と、ミュート切り替え機能も解説。プロンプト付き。

Claude Codeの「待ち時間」「放置時間」問題

Claude Codeを複数ターミナルで並列に動かす運用は生産性が高い反面、ある問題が起こります。

フロントの修正、APIのリファクタ、テストの追加──3つのセッションを同時に走らせていると、あるセッションが完了したことに気づかず数分〜十数分放置してしまう。通知音を設定していても、別の作業に集中していると1回の音は聞き逃しがちです。

この「待ち時間」「放置時間」の積み重ねが、並列作業の生産性を大きく下げています。

この記事では、Claude CodeのHooks機能とsayコマンドを使ってClaude Codeに喋らせて状況を教えてもらい、放置が続けば繰り返しリマインドし、応答したら自動で止まる仕組みを実装します。

基本の通知設定がまだの方へ

この記事はアイドルリマインダに特化した内容です。Claude Codeの基本的な通知音設定(効果音+プロジェクト名読み上げ)がまだの方は、先にこちらの記事を参考にしてください。

Claude Codeの進捗を「音+案件名」で通知する

通知リマインダの仕組み

3つのHooksイベントを組み合わせて、Claude Codeに喋らせます。

使用するイベント
イベント発火タイミング役割
Stopタスク完了時音声通知+リマインダ起動
Notification質問・確認待ち時音声通知+リマインダ起動
PreToolUseユーザー応答後、ツール実行前リマインダ停止
イベント発火タイミング役割
Stopタスク完了時音声通知+リマインダ起動
Notification質問・確認待ち時音声通知+リマインダ起動
PreToolUseユーザー応答後、ツール実行前リマインダ停止
動作の流れ
text
[タスク完了] → 音声通知(効果音+「my-app 完了」と喋る)

              2分経過 → リマインド(Ping音+「my-app まだ待っています」)

              2分経過 → リマインド

              ...繰り返し...

          [ユーザーが応答] → PreToolUse発火 → リマインダ停止
text
[タスク完了] → 音声通知(効果音+「my-app 完了」と喋る)

              2分経過 → リマインド(Ping音+「my-app まだ待っています」)

              2分経過 → リマインド

              ...繰り返し...

          [ユーザーが応答] → PreToolUse発火 → リマインダ停止

PreToolUseがポイントです。Claude Codeがツールを使う直前に発火するイベントなので、「ユーザーが戻ってきてClaude Codeが再び動き出した」タイミングを検知できます。

実装

2つのシェルスクリプトを作成し、settings.jsonで接続します。

notify-idle.sh(音声通知+リマインダ起動)

~/.claude/scripts/notify-idle.sh として保存します。

bash
#!/bin/bash
SOUND="${1:-Hero}"
MESSAGE="${2:-完了}"
PROJECT="$(basename "$PWD")"
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

# 既存のリマインダがあればkill
if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

# 即時通知(バックグラウンドで実行し、hookをブロックしない)
(
  afplay "/System/Library/Sounds/${SOUND}.aiff"
  say "${PROJECT} ${MESSAGE}"
) > /dev/null 2>&1 &

# リマインダループ(120秒ごと)
(
  while true; do
    sleep 120
    afplay /System/Library/Sounds/Ping.aiff
    say "${PROJECT} まだ待っています"
  done
) > /dev/null 2>&1 &

echo $! > "$PIDFILE"
bash
#!/bin/bash
SOUND="${1:-Hero}"
MESSAGE="${2:-完了}"
PROJECT="$(basename "$PWD")"
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

# 既存のリマインダがあればkill
if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

# 即時通知(バックグラウンドで実行し、hookをブロックしない)
(
  afplay "/System/Library/Sounds/${SOUND}.aiff"
  say "${PROJECT} ${MESSAGE}"
) > /dev/null 2>&1 &

# リマインダループ(120秒ごと)
(
  while true; do
    sleep 120
    afplay /System/Library/Sounds/Ping.aiff
    say "${PROJECT} まだ待っています"
  done
) > /dev/null 2>&1 &

echo $! > "$PIDFILE"
cancel-idle.sh(リマインダ停止)

~/.claude/scripts/cancel-idle.sh として保存します。

bash
#!/bin/bash
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi
bash
#!/bin/bash
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi
実行権限の付与
bash
chmod +x ~/.claude/scripts/notify-idle.sh ~/.claude/scripts/cancel-idle.sh
bash
chmod +x ~/.claude/scripts/notify-idle.sh ~/.claude/scripts/cancel-idle.sh
settings.jsonの設定

~/.claude/settings.jsonhooksに以下を追加します。

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Hero 完了"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Glass 質問があります"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/cancel-idle.sh"
          }
        ]
      }
    ]
  }
}
json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Hero 完了"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Glass 質問があります"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/cancel-idle.sh"
          }
        ]
      }
    ]
  }
}
実装のポイント

パイプの切り離しが必須

> /dev/null 2>&1 & でバックグラウンドプロセスのstdout/stderrを切り離しています。これを省略すると、バックグラウンドプロセスが親のパイプを握り続け、Claude Codeのhookシステムが完了を待ち続けてフリーズします。

PIDファイルによるプロセス管理

PIDファイル名に$(echo "$PWD" | md5)を使うことで、プロジェクトごとに独立したリマインダを管理できます。プロジェクトAの完了通知がプロジェクトBのリマインダを停止してしまう、といった問題が起きません。

リマインド間隔の調整

sleep 120(120秒=2分)がデフォルトのリマインド間隔です。頻繁すぎると感じたら値を大きく、もっと早く気づきたい場合は小さくしてください。

セッション終了後もリマインダが鳴り続ける問題

この仕組みには落とし穴があります。リマインダのループはバックグラウンドの独立プロセスとして動作するため、Claude Codeのセッションを閉じてもリマインダは鳴り続けます。

cancel-idle.shPreToolUseフックでしか呼ばれないため、Claude Codeを終了した後は誰もリマインダを止めてくれません。ターミナルを閉じた後も2分おきに「まだ待っています」と喋り続ける、という状態になります。

対策1:リマインダループ内でプロセスチェック

ループの中でClaudeプロセスの存在を確認し、なければ自動終了します。

bash
# リマインダループ(120秒ごと、Claudeプロセスが存在する間のみ)
(
  while true; do
    sleep 120
    # Claudeプロセスが存在しなければ自動終了
    if ! pgrep -f "claude" > /dev/null 2>&1; then
      rm -f "$PIDFILE"
      exit 0
    fi
    afplay /System/Library/Sounds/Ping.aiff
    say "${REMIND_MESSAGE}"
  done
) > /dev/null 2>&1 &
bash
# リマインダループ(120秒ごと、Claudeプロセスが存在する間のみ)
(
  while true; do
    sleep 120
    # Claudeプロセスが存在しなければ自動終了
    if ! pgrep -f "claude" > /dev/null 2>&1; then
      rm -f "$PIDFILE"
      exit 0
    fi
    afplay /System/Library/Sounds/Ping.aiff
    say "${REMIND_MESSAGE}"
  done
) > /dev/null 2>&1 &

pgrep -f "claude" でClaudeのプロセスが存在するかチェックし、見つからなければPIDファイルを削除してループを終了します。最大120秒の遅延はありますが、放置され続けることはなくなります。

対策2:StopフックでCancel → Notifyの順に実行

Stopイベントのcommandを変更し、前回のリマインダをキャンセルしてから新しい通知を起動するようにします。

json
"Stop": [
  {
    "hooks": [
      {
        "type": "command",
        "command": "~/.claude/scripts/cancel-idle.sh && ~/.claude/scripts/notify-idle.sh Hero 完了"
      }
    ]
  }
]
json
"Stop": [
  {
    "hooks": [
      {
        "type": "command",
        "command": "~/.claude/scripts/cancel-idle.sh && ~/.claude/scripts/notify-idle.sh Hero 完了"
      }
    ]
  }
]

notify-idle.shの中でも既存リマインダのkill処理はありますが、cancel-idle.shを明示的に先に呼ぶことで、確実に前回のリマインダが止まった状態から開始されます。

両方の対策を併用すれば、正常終了時はStopフックで、異常終了時はプロセスチェックで、それぞれリマインダが自動停止します。

サウンドのミュート切り替え

会議中やカフェで作業しているとき、一時的に音を止めたい場面があります。フラグファイルの有無で音声のON/OFFを切り替える仕組みを追加できます。

toggle-sound.sh(ミュート切り替え)

~/.claude/scripts/toggle-sound.sh として保存します。

bash
#!/bin/bash
MUTEFILE="/tmp/claude-sound-muted"

if [ "$1" = "on" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
elif [ "$1" = "off" ]; then
  touch "$MUTEFILE"
  echo "サウンド OFF"
elif [ -f "$MUTEFILE" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
else
  touch "$MUTEFILE"
  echo "サウンド OFF"
fi
bash
#!/bin/bash
MUTEFILE="/tmp/claude-sound-muted"

if [ "$1" = "on" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
elif [ "$1" = "off" ]; then
  touch "$MUTEFILE"
  echo "サウンド OFF"
elif [ -f "$MUTEFILE" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
else
  touch "$MUTEFILE"
  echo "サウンド OFF"
fi
bash
chmod +x ~/.claude/scripts/toggle-sound.sh
bash
chmod +x ~/.claude/scripts/toggle-sound.sh
使い方
bash
# 音を消す
~/.claude/scripts/toggle-sound.sh off

# 音をつける
~/.claude/scripts/toggle-sound.sh on

# トグル(引数なしで切り替え)
~/.claude/scripts/toggle-sound.sh
bash
# 音を消す
~/.claude/scripts/toggle-sound.sh off

# 音をつける
~/.claude/scripts/toggle-sound.sh on

# トグル(引数なしで切り替え)
~/.claude/scripts/toggle-sound.sh

シェルのエイリアスを設定しておくと便利です。

bash
# ~/.zshrc に追加
alias csound="~/.claude/scripts/toggle-sound.sh"
bash
# ~/.zshrc に追加
alias csound="~/.claude/scripts/toggle-sound.sh"
notify-idle.shへのミュート対応

notify-idle.shにフラグファイルのチェックを追加します。

bash
MUTEFILE="/tmp/claude-sound-muted"

# 即時通知
(
  if [ ! -f "$MUTEFILE" ]; then
    afplay "/System/Library/Sounds/${SOUND}.aiff"
    say "${SAY_MESSAGE}"
  fi
) > /dev/null 2>&1 &

# リマインダループ
(
  while true; do
    sleep 120
    if ! pgrep -f "claude" > /dev/null 2>&1; then
      rm -f "$PIDFILE"
      exit 0
    fi
    if [ ! -f "$MUTEFILE" ]; then
      afplay /System/Library/Sounds/Ping.aiff
      say "${REMIND_MESSAGE}"
    fi
  done
) > /dev/null 2>&1 &
bash
MUTEFILE="/tmp/claude-sound-muted"

# 即時通知
(
  if [ ! -f "$MUTEFILE" ]; then
    afplay "/System/Library/Sounds/${SOUND}.aiff"
    say "${SAY_MESSAGE}"
  fi
) > /dev/null 2>&1 &

# リマインダループ
(
  while true; do
    sleep 120
    if ! pgrep -f "claude" > /dev/null 2>&1; then
      rm -f "$PIDFILE"
      exit 0
    fi
    if [ ! -f "$MUTEFILE" ]; then
      afplay /System/Library/Sounds/Ping.aiff
      say "${REMIND_MESSAGE}"
    fi
  done
) > /dev/null 2>&1 &

ミュート中もリマインダループ自体は動き続けます。/tmp/claude-sound-mutedが消えた瞬間から音が復活するので、フック設定の変更やClaude Codeの再起動は不要です。

Agent Teams対応:チームメンバー名も読み上げる

Claude Codeのagent teams機能で複数のteammateを協調させている場合、「どのメンバーが完了したか」も知りたくなります。

Hooksはstdinにイベント情報のJSONを受け取ります。チーム関連のイベントにはteammate_nameが含まれるため、これを読み取ってメッセージに含めることができます。

チーム関連のHooksイベント
イベント発火タイミング含まれる情報
TeammateIdleteammateがアイドル状態になった時teammate_name, team_name
TaskCompletedteammateがタスクを完了した時teammate_name, team_name, task_subject
イベント発火タイミング含まれる情報
TeammateIdleteammateがアイドル状態になった時teammate_name, team_name
TaskCompletedteammateがタスクを完了した時teammate_name, team_name, task_subject
notify-idle.shの拡張

stdinのJSONからteammate_nameを抽出する処理を追加します。

bash
#!/bin/bash
SOUND="${1:-Hero}"
MESSAGE="${2:-完了}"
PROJECT="$(basename "$PWD")"
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

# stdinからJSONを読み取り、teammate_nameを抽出
INPUT=$(cat 2>/dev/null || echo "{}")
TEAMMATE=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('teammate_name',''))" 2>/dev/null)

# teammate_nameがあればメッセージに含める
if [ -n "$TEAMMATE" ]; then
  SAY_MESSAGE="${PROJECT} ${TEAMMATE} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} ${TEAMMATE} まだ待っています"
else
  SAY_MESSAGE="${PROJECT} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} まだ待っています"
fi

# 既存のリマインダがあればkill
if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

# 即時通知
(
  afplay "/System/Library/Sounds/${SOUND}.aiff"
  say "${SAY_MESSAGE}"
) > /dev/null 2>&1 &

# リマインダループ
(
  while true; do
    sleep 120
    afplay /System/Library/Sounds/Ping.aiff
    say "${REMIND_MESSAGE}"
  done
) > /dev/null 2>&1 &

echo $! > "$PIDFILE"
bash
#!/bin/bash
SOUND="${1:-Hero}"
MESSAGE="${2:-完了}"
PROJECT="$(basename "$PWD")"
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

# stdinからJSONを読み取り、teammate_nameを抽出
INPUT=$(cat 2>/dev/null || echo "{}")
TEAMMATE=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('teammate_name',''))" 2>/dev/null)

# teammate_nameがあればメッセージに含める
if [ -n "$TEAMMATE" ]; then
  SAY_MESSAGE="${PROJECT} ${TEAMMATE} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} ${TEAMMATE} まだ待っています"
else
  SAY_MESSAGE="${PROJECT} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} まだ待っています"
fi

# 既存のリマインダがあればkill
if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

# 即時通知
(
  afplay "/System/Library/Sounds/${SOUND}.aiff"
  say "${SAY_MESSAGE}"
) > /dev/null 2>&1 &

# リマインダループ
(
  while true; do
    sleep 120
    afplay /System/Library/Sounds/Ping.aiff
    say "${REMIND_MESSAGE}"
  done
) > /dev/null 2>&1 &

echo $! > "$PIDFILE"
settings.jsonへの追加

既存のhooksにTeammateIdleTaskCompletedを追加します。

json
{
  "hooks": {
    "TeammateIdle": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Ping アイドル状態です"
          }
        ]
      }
    ],
    "TaskCompleted": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Hero タスク完了"
          }
        ]
      }
    ]
  }
}
json
{
  "hooks": {
    "TeammateIdle": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Ping アイドル状態です"
          }
        ]
      }
    ],
    "TaskCompleted": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/scripts/notify-idle.sh Hero タスク完了"
          }
        ]
      }
    ]
  }
}
読み上げの例
イベント通常agent teams利用時
Stop「my-app 完了」「my-app 完了」
TeammateIdle-「my-app researcher アイドル状態です」
TaskCompleted-「my-app implementer タスク完了」
リマインド「my-app まだ待っています」「my-app researcher まだ待っています」
イベント通常agent teams利用時
Stop「my-app 完了」「my-app 完了」
TeammateIdle-「my-app researcher アイドル状態です」
TaskCompleted-「my-app implementer タスク完了」
リマインド「my-app まだ待っています」「my-app researcher まだ待っています」

agent teamsを使っていない場合はteammate_nameが空になるため、今まで通りプロジェクト名だけが読み上げられます。既存の設定と完全に互換です。

コピペで導入:Claude Code用セットアッププロンプト

以下のプロンプトをClaude Codeにそのまま貼り付ければ、スクリプトの作成からsettings.jsonの設定まで自動で行ってくれます。

text
Claude Codeの通知リマインダをセットアップして。以下の通りに実装してほしい。

■ 1. ~/.claude/scripts/notify-idle.sh を作成(実行権限付き)

#!/bin/bash
SOUND="${1:-Hero}"
MESSAGE="${2:-完了}"
PROJECT="$(basename "$PWD")"
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"
MUTEFILE="/tmp/claude-sound-muted"

# stdinからJSONを読み取り、teammate_nameを抽出(agent teams対応)
INPUT=$(cat 2>/dev/null || echo "{}")
TEAMMATE=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('teammate_name',''))" 2>/dev/null)

if [ -n "$TEAMMATE" ]; then
  SAY_MESSAGE="${PROJECT} ${TEAMMATE} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} ${TEAMMATE} まだ待っています"
else
  SAY_MESSAGE="${PROJECT} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} まだ待っています"
fi

if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

(
  if [ ! -f "$MUTEFILE" ]; then
    afplay "/System/Library/Sounds/${SOUND}.aiff"
    say "${SAY_MESSAGE}"
  fi
) > /dev/null 2>&1 &

(
  while true; do
    sleep 120
    if ! pgrep -f "claude" > /dev/null 2>&1; then
      rm -f "$PIDFILE"
      exit 0
    fi
    if [ ! -f "$MUTEFILE" ]; then
      afplay /System/Library/Sounds/Ping.aiff
      say "${REMIND_MESSAGE}"
    fi
  done
) > /dev/null 2>&1 &

echo $! > "$PIDFILE"

■ 2. ~/.claude/scripts/cancel-idle.sh を作成(実行権限付き)

#!/bin/bash
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

■ 3. ~/.claude/scripts/toggle-sound.sh を作成(実行権限付き)

#!/bin/bash
MUTEFILE="/tmp/claude-sound-muted"

if [ "$1" = "on" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
elif [ "$1" = "off" ]; then
  touch "$MUTEFILE"
  echo "サウンド OFF"
elif [ -f "$MUTEFILE" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
else
  touch "$MUTEFILE"
  echo "サウンド OFF"
fi

■ 4. ~/.claude/settings.json のhooksに以下を設定

- Stop: ~/.claude/scripts/cancel-idle.sh && ~/.claude/scripts/notify-idle.sh Hero 完了
- Notification(matcher空文字): ~/.claude/scripts/notify-idle.sh Glass 質問があります
- PreToolUse(matcher空文字): ~/.claude/scripts/cancel-idle.sh
- TeammateIdle: ~/.claude/scripts/notify-idle.sh Ping アイドル状態です
- TaskCompleted: ~/.claude/scripts/notify-idle.sh Hero タスク完了

既存のsettings.jsonの設定はそのまま残してhooksだけ追加・更新して。
text
Claude Codeの通知リマインダをセットアップして。以下の通りに実装してほしい。

■ 1. ~/.claude/scripts/notify-idle.sh を作成(実行権限付き)

#!/bin/bash
SOUND="${1:-Hero}"
MESSAGE="${2:-完了}"
PROJECT="$(basename "$PWD")"
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"
MUTEFILE="/tmp/claude-sound-muted"

# stdinからJSONを読み取り、teammate_nameを抽出(agent teams対応)
INPUT=$(cat 2>/dev/null || echo "{}")
TEAMMATE=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('teammate_name',''))" 2>/dev/null)

if [ -n "$TEAMMATE" ]; then
  SAY_MESSAGE="${PROJECT} ${TEAMMATE} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} ${TEAMMATE} まだ待っています"
else
  SAY_MESSAGE="${PROJECT} ${MESSAGE}"
  REMIND_MESSAGE="${PROJECT} まだ待っています"
fi

if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

(
  if [ ! -f "$MUTEFILE" ]; then
    afplay "/System/Library/Sounds/${SOUND}.aiff"
    say "${SAY_MESSAGE}"
  fi
) > /dev/null 2>&1 &

(
  while true; do
    sleep 120
    if ! pgrep -f "claude" > /dev/null 2>&1; then
      rm -f "$PIDFILE"
      exit 0
    fi
    if [ ! -f "$MUTEFILE" ]; then
      afplay /System/Library/Sounds/Ping.aiff
      say "${REMIND_MESSAGE}"
    fi
  done
) > /dev/null 2>&1 &

echo $! > "$PIDFILE"

■ 2. ~/.claude/scripts/cancel-idle.sh を作成(実行権限付き)

#!/bin/bash
PIDFILE="/tmp/claude-idle-$(echo "$PWD" | md5).pid"

if [ -f "$PIDFILE" ]; then
  kill "$(cat "$PIDFILE")" 2>/dev/null
  rm -f "$PIDFILE"
fi

■ 3. ~/.claude/scripts/toggle-sound.sh を作成(実行権限付き)

#!/bin/bash
MUTEFILE="/tmp/claude-sound-muted"

if [ "$1" = "on" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
elif [ "$1" = "off" ]; then
  touch "$MUTEFILE"
  echo "サウンド OFF"
elif [ -f "$MUTEFILE" ]; then
  rm -f "$MUTEFILE"
  echo "サウンド ON"
else
  touch "$MUTEFILE"
  echo "サウンド OFF"
fi

■ 4. ~/.claude/settings.json のhooksに以下を設定

- Stop: ~/.claude/scripts/cancel-idle.sh && ~/.claude/scripts/notify-idle.sh Hero 完了
- Notification(matcher空文字): ~/.claude/scripts/notify-idle.sh Glass 質問があります
- PreToolUse(matcher空文字): ~/.claude/scripts/cancel-idle.sh
- TeammateIdle: ~/.claude/scripts/notify-idle.sh Ping アイドル状態です
- TaskCompleted: ~/.claude/scripts/notify-idle.sh Hero タスク完了

既存のsettings.jsonの設定はそのまま残してhooksだけ追加・更新して。
まとめ

Claude Codeの「待ち時間」「放置時間」は、Hooksとsayコマンドの組み合わせで解消できます。

  • Stop / Notification で音声通知とリマインダループを起動
  • PreToolUse でユーザー応答を検知してリマインダを自動停止
  • PIDファイルでプロジェクトごとに独立管理
  • > /dev/null 2>&1 & でhookのブロックを回避
  • pgrepによるプロセスチェックで、セッション終了後のリマインダ残留を防止
  • Stopフックでcancel-idle.shを先に呼び、前回のリマインダを確実に停止
  • フラグファイルベースのミュート切り替えで、会議中や外出先でもサッとON/OFFできる

Claude Codeに喋らせることで、セッションを3つ、4つと増やしても画面を見ずに状況を把握できます。

参考リンク

この記事が参考になったら

この記事が役に立ったと思いましたら、よろしければフォローをお願いします。

Xでフォローする

Claude Codeを活用した開発効率化やAIワークフローの構築に興味がありましたら、お気軽にご連絡ください。

お問い合わせ

この記事を書いた人

Webプランナー、開発会社の創業メンバーを経て、Fintech・DX・Web3の企画・開発に従事。2021年に独立し、2023年に株式会社hanzochangを設立。現在はAIを活用した業務改善と、自社サービス「指定管理者制度AI」の開発・運用に取り組んでいます。

半澤勇大のXアカウント(新しいタブで開く)

この記事の株式会社hanzochangについて
少し紹介させてください!

この記事に関連する当社のサービス

業務改善・AI活用支援

転記・集計・資料作成にかかる時間を減らしませんか?

現在の業務と使っているシステムを整理し、AIが役立つ作業から改善します。試作で効果を確かめ、現場で使うアプリの開発・運用まで支援します。

サービスの詳細を見る
Jくんが多様な業務アプリを開発するイラスト。背景に多数のアプリが並ぶ。
  • 業務の整理

    手間のかかる作業と、改善する順番を明確に。

  • 試作・アプリ開発

    実際の手順に合わせて、使える仕組みを開発。

  • 導入・運用支援

    使い方の共有から、導入後の改善まで対応。

業務改善・AI活用について、お気軽にご相談ください。