🎶

Claude Codeで音楽駆動開発したい!

に公開

みなさま、Claude Codeは活用していますでしょうか。
先日、Claude CodeのHook機能がリリースされてから、タスク終了時の通知音を簡単に鳴らせるようになり、多くの方が喜んでいる様子を見かけます。

ただ、個人的には通知音を鳴らすだけは味気なく感じています。
Miroのタイマー機能のように、プロンプト実行中は温もりのあるBGMが鳴るといいなと思ったので、音楽駆動開発(音楽を聴くためにClaude Codeに開発をさせる)できないか試してみました。
実現したいことは「Claude Codeがプロンプト実行中に音楽を再生し、終わったら音楽を停止する。」です。

対象読者

Claude Codeのプロンプト実行中にBGMを流したい人
Claude Codeでバックグラウンド処理を待機させず続行させる方法を知りたい人

縛り

  • 端末: Mac M2 Pro
  • Claude Codeのバージョン: v1.0.48 ~ v1.0.54
    • 検証期間中に自動バージョンアップが行われたため、詳細なバージョンは不明です。
  • 再生ツール: Macに標準搭載されているafplayコマンドで実現可能な範囲でやります。
  • その他の制約: 複数のリポジトリで動作させた場合のことは考慮しません。

Claude Codeにおける制限事項

いくつかの制限事項があります。

  1. バックグラウンド処理を実行すると、Claude Codeはその処理が終わるまで待機してしまう
  2. ユーザーが承認したイベントに対応するフックが存在しない
  3. フック間で環境変数が引き継げない(一時停止・再開機能を実装する場合)

バックグラウンド処理を実行すると、Claude Codeはその処理が終わるまで待機してしまう

検証時点でのバージョンでは、Claude Codeでバックグラウンド処理を実行すると、その処理が完了するまで待機してしまいます。

settings.json
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay ~/xxxx.mp3 &"
          }
        ]
      }
    ],
  }

この問題は、標準出力と標準エラー出力を破棄してから、バックグラウンド実行( [コマンド] > /dev/null 2>&1 &)することで、Claude Codeに待機させず処理を進められたため、これを使います。

settings.json
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay ~/xxxx.mp3 > /dev/null 2>&1 &"
          }
        ]
      }
    ],
  }

ユーザーが承認したイベントに対応するフックが存在しない

permissionで許可していない操作については、ユーザーに承認を求めるダイアログが表示されます。

ユーザーの対応が必要な時はBGMを停止したいのですが、検証時点では承認に対応するフックが存在しません。そのため、NotificationフックでBGMを停止して、Claude Codeが再開した時にBGMを再開する機能は見送ります。

フック間で環境変数が引き継げない(一時停止・再開機能を実装する場合)

フック間で環境変数を引き継ぐことができません。以下の例で確認できます。

settings.json
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "nohup ls & export ls_pid=$! && echo UserPromptSubmit: $ls_pid >> .claude/user_prompt_submit.log"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo Stop: $ls_pid >> .claude/stop.log"
          }
        ]
      }
    ],
  }
user_prompt_submit.log
UserPromptSubmit: 7293
stop.log
Stop:

afplayには一時停止機能がなく、再開時は最初から再生し直す必要があります。killコマンドで一時停止や再開をしたい場合にはプロセスIDの特定が必要ですが、フック間で環境変数を引き継げないため別の方法を検討する必要があります。
(停止する場合は、すべてのafplayのプロセスをkillコマンドで終了させてもいいですが、力技感あるのでプロセスID指定で停止したいです。)

この問題は、プロセスIDを記録したファイルを出力することで回避することとします。

UserPromptSubmitのコマンド
nohup afplay -v 0.1 ~/xxxx.mp3 > /dev/null 2>&1 & echo $! > .claude/music_pid"
Stopのコマンド
kill $(cat .claude/music_pid)

やってみた

BGMは各自でご用意ください。私はBGMerさんで、作業の邪魔にならず、気持ちの上がる曲をダウンロードして使用しています。これとかこれが好きです。

シンプルな設定(一時停止なし)

以下の設定を行うことで、プロンプト実行時にBGMが再生され、プロンプトの実行が終わるとBGMが停止します。

settings.json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "nohup afplay -v 0.1 ~/xxxx.mp3 > /dev/null 2>&1 & echo $! > .claude/music_pid"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "kill $(cat .claude/music_pid) || true"
          }
        ]
      }
    ]
  }
}

一時停止、再開させる設定

ワンライナーでは複雑になるために、シェルスクリプトにしました。(Claude Codeに作成してもらいました。)引数で指定したファイルか、~/.claude/musicディレクトリにあるファイルをランダムで再生します。
一時停止、再開が不要でランダム再生したいだけなら、Stopフックをkill $(cat .claude/music_pid)にするだけで実現できます。

play_bgm.sh
#!/bin/bash

# バックグラウンド実行用スクリプト
# 使用方法: ./play_bg.sh [音楽ファイルのパス]

MUSIC_FILE=$1
PID_FILE=".claude/music_pid"

# MUSIC_FILEの指定がない場合ランダムな音楽を選択
if [ -z "$MUSIC_FILE" ]; then
    MUSIC_DIR=`echo ~/.claude/music`
    # ディレクトリが存在しない場合はエラー
    if [ ! -d "$MUSIC_DIR" ]; then
        echo "音楽ディレクトリが存在しません: $MUSIC_DIR"
        exit 1
    fi
    # todo: ランダムな音楽ファイル(.mp3)を選択。shufは使わない
    MUSIC_FILE=$(find "$MUSIC_DIR" -type f -name "*.mp3" | sort -R | head -n 1)
    # 音楽ファイルが見つからない場合はエラー
    if [ -z "$MUSIC_FILE" ]; then
        echo "音楽ファイルが見つかりません: $MUSIC_DIR"
        exit 1
    fi
fi

# 既存のPIDが存在するかチェック
if [ -f "$PID_FILE" ]; then
    EXISTING_PID=$(cat "$PID_FILE")
    # プロセスが存在し、かつafplayプロセスかチェック
    if kill -0 "$EXISTING_PID" 2>/dev/null && ps -p "$EXISTING_PID" | grep -q afplay; then
        # 既存のafplayプロセスを再開(一時停止状態の場合)
        kill -CONT "$EXISTING_PID" 2>/dev/null
        echo "既存の音楽プロセスを再開: PID $EXISTING_PID"
        exit 0
    else
        # 無効なPIDの場合は削除
        rm -f "$PID_FILE"
    fi
fi

# 既存のafplayプロセスを停止
pkill -f afplay

# バックグラウンドでafplayを実行
afplay $MUSIC_FILE -v 0.2 > /dev/null 2>&1 &

echo "音楽をバックグラウンドで再生開始: $MUSIC_FILE"
export MUSIC_PID=$!
echo "PID: $MUSIC_PID"
echo $MUSIC_PID > "$PID_FILE"
stop_bg.sh
#!/bin/bash

if [ -f .claude/music_pid ]; then
    export MUSIC_PID=$(cat .claude/music_pid | head -1)
    if [ -n "$MUSIC_PID" ] && kill -0 "$MUSIC_PID" 2>/dev/null; then
        kill -STOP "$MUSIC_PID" && echo "音楽を停止しました: PID $MUSIC_PID"
    fi
fi

課題

プロセス制御による一時停止時の音声の問題

killコマンドで停止と再開をすることで、BGMの一時停止再開が再現はできますが、停止時に音楽再生中に有線イヤホンを無理やり引っこ抜いたかのような不快な音がしたり、slackの通知音のスココの音が正常に再生されなくなるなど、部分的に音声の問題が発生します。

並行実行を考慮していない

Claude Codeの並列実行させた場合、起動数に応じて複数の曲が再生されてしまいます。
プロセスIDのファイルを使うことで、リポジトリごとに制御ができるものの、実用的ではないです。

まとめ

Claude CodeのHook機能を活用することで、プロンプト実行中にBGMを流す「音楽駆動開発」を実現できました。いくつかの技術的制限や課題はありますが、やりたいことができて満足です。
今後のClaude Codeのアップデートで、より柔軟なフック機能が追加されることを期待しています!

Discussion