はじめに
AIコーディングエージェントの Claude Code、爆速で開発が進んで最高ですね。
しかし、重いタスクを投げている間、ずっとターミナルを眺めているのは少しもったいない気がします。別の作業をしていると、いつの間にか処理が終わっていたり、エラーで止まっていたりすることもあります。
事の始まりは、某所で開催された「AI駆動開発」の勉強会に参加したときのことです。講師の方がClaude Codeの完了通知で「ずんだもん」を喋らせているのを見て、
これめちゃくちゃいいな。自分も真似したい!
と強く惹かれました。
そこで、Claude Codeの hooks(通知機能)を利用して、タスクの成功・失敗を音声で教えてもらう環境を作ることにしました。
ただ、世の中のこの手のサンプルを見ると、VOICEVOXはだいたい「ずんだもん」に喋らせています。もちろんずんだもんも可愛いのですが、おじさん世代としては、どこか親近感のわく 「ちび式じい」 を相棒に選びたいところです。
渋い老紳士の声で「お疲れさんじゃ」と労ってもらう方が、個人的にはしっくり馴染みます。
本記事では、WSL2上のClaude CodeからWindows側で動作するVOICEVOXを呼び出し、「ちび式じい」に作業完了を通知してもらう環境の構築手順を紹介します。
構成イメージ
Claude CodeのHookイベントをトリガーにPythonスクリプトを実行し、Windows側で動作するVOICEVOX EngineのHTTP APIを呼び出します。
生成した音声ファイルはWindowsのTempフォルダへ保存し、PowerShell経由で再生します。
Claude Code
↓ Hook
voicevox.py (WSL2)
↓ HTTP API
VOICEVOX Engine (Windows)
↓ WAV生成
Windows Temp
↓
PowerShell再生
1. Windows側:VOICEVOXエンジンの準備
VOICEVOXのGUIを起動するだけでもAPIは利用できますが、デフォルトでは localhost (127.0.0.1) にバインドされています。
WSL2からアクセスできるよう、Windowsホストのネットワークインターフェースで待ち受けるようにエンジンを起動します。
PowerShell(またはコマンドプロンプト)を開き、以下を実行します。
# インストールパスは環境に応じて変更してください
& "$env:LOCALAPPDATA\Programs\VOICEVOX\vv-engine\run.exe" --host 0.0.0.0
💡 毎回手動で起動するのが面倒な場合は、スタートアップフォルダ(
shell:startup)に登録しておくと便利です。
2. WSL2側:Claude Code Hooksの設定
Claude Codeの設定ファイル(~/.claude/config.json)を編集し、タスク成功時と失敗時にPythonスクリプトを呼び出します。
{
"hooks": {
"onSuccess": "python3 ~/.claude/hooks/voicevox.py success",
"onFailure": "python3 ~/.claude/hooks/voicevox.py failure"
}
}
3. 通知用Pythonスクリプトの作成
WSL2ではWindowsホストのIPアドレスが固定ではなく、Windowsの再起動などで変わることがあります。
/etc/resolv.conf を参照してホストIPを取得する方法も見かけますが、環境によってはDNSサーバーのアドレスが返る場合があります。
今回はLinuxのルーティングテーブルからデフォルトゲートウェイを取得し、WindowsホストのIPアドレスとして利用するようにしました。
~/.claude/hooks/voicevox.py を作成し、以下のコードを保存します。
※ WIN_TEMP_WAV_PATH のユーザー名部分はご自身の環境に合わせて変更してください。
import sys
import json
import requests
import subprocess
import re
def get_windows_ip():
"""Windowsホスト(デフォルトゲートウェイ)のIPを取得"""
try:
result = subprocess.run(
["ip", "route"],
capture_output=True,
text=True,
check=True
)
for line in result.stdout.splitlines():
if line.startswith("default via"):
match = re.search(r"default via\s+([\d\.]+)", line)
if match:
return match.group(1)
except Exception:
pass
return "127.0.0.1"
WIN_IP = get_windows_ip()
VOICEVOX_URL = f"http://{WIN_IP}:50021"
# ちび式じい(ノーマル)
SPEAKER_JI_I = 42
MAX_CHARS = 100
WIN_TEMP_WAV_PATH = (
"/mnt/c/Users/<WindowsUser>/AppData/Local/Temp/voicevox_output.wav"
)
def play_voice(text, speaker_id):
if not text.strip():
return
if len(text) > MAX_CHARS:
text = text[:MAX_CHARS] + "、以下は省略するぞ。"
try:
# 音声クエリ生成
query_res = requests.post(
f"{VOICEVOX_URL}/audio_query",
params={
"text": text,
"speaker": speaker_id
},
timeout=3
)
if query_res.status_code != 200:
return
# 音声生成
synth_res = requests.post(
f"{VOICEVOX_URL}/synthesis",
params={"speaker": speaker_id},
json=query_res.json(),
timeout=10
)
if synth_res.status_code != 200:
return
# Windows側へ保存
with open(WIN_TEMP_WAV_PATH, "wb") as f:
f.write(synth_res.content)
# PowerShellで再生
ps_command = (
"[System.Media.SoundPlayer]::new("
"[System.IO.Path]::Combine("
"$env:USERPROFILE,"
"'AppData','Local','Temp','voicevox_output.wav'"
")).PlaySync()"
)
subprocess.Popen(
[
"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
"-Command",
ps_command
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
except Exception:
pass
if __name__ == "__main__":
status = sys.argv[1] if len(sys.argv) > 1 else "success"
try:
input_data = sys.stdin.read()
if input_data:
event_json = json.loads(input_data)
summary = event_json.get("summary", "")
if status == "failure":
text_to_speak = (
f"おっと、しくじったようじゃな。"
f"{summary}。"
f"ログを確認してみておくれ。"
)
else:
if summary:
text_to_speak = (
f"{summary}、じゃな。"
f"完了したぞ。"
)
else:
text_to_speak = (
"タスクが完了したぞ。"
"お疲れさんじゃ。"
)
play_voice(text_to_speak, SPEAKER_JI_I)
except Exception:
sys.exit(0)
実装時のポイント
WindowsホストIPを自動取得する
WSL2ではWindowsホストのIPアドレスが変わることがあります。
そのため、IPアドレスを固定値で記述するのではなく、ルーティングテーブルからデフォルトゲートウェイを取得するようにしています。
これにより、Windows再起動後も設定変更なしで動作します。
日本語テキストの送信
VOICEVOX APIへ日本語を送信する際は、requests の params を利用しています。
URLエンコードをライブラリに任せることで、日本語を含むテキストでも安全に送信できます。
音声ファイルはWindows側へ保存する
生成したWAVファイルはWSL2内ではなく、WindowsのTempフォルダへ直接保存しています。
これにより、そのままPowerShellから再生できます。
Windowsパスを安全に扱う
WindowsパスをPython文字列で扱う際は、エスケープシーケンスによる意図しない解釈が発生することがあります。
そのため、PowerShell側で Path.Combine() を利用してパスを組み立てています。
4. 単体テスト
設定が正しく動作するか確認します。
WindowsホストのIPアドレスは自動取得されるため、PC再起動後もそのまま利用できます。
成功通知
echo '{"summary":"ビルドが正常に終了した"}' \
| python3 ~/.claude/hooks/voicevox.py success
以下のような音声が再生されれば成功です。
「ビルドが正常に終了した、じゃな。完了したぞ。」
失敗通知
echo '{"summary":"テストコードが1件失敗"}' \
| python3 ~/.claude/hooks/voicevox.py failure
以下のような音声が再生されます。
「おっと、しくじったようじゃな。テストコードが1件失敗。ログを確認してみておくれ。」
おわりに
これで、Claude Codeに長時間のリファクタリングやデバッグ作業を任せている間でも、完了や失敗を音声で把握できるようになりました。
通知音でも十分便利ですが、キャラクター音声で状況を伝えてもらうと意外と愛着が湧きます。
今回は「ちび式じい」を選びましたが、お気に入りのキャラクターを相棒にしてみるのも面白いと思います。
AIエージェントによる開発が当たり前になりつつある今、音声通知を組み合わせることで、さらに快適な開発環境を作ることができました。
おまけ:利用可能なキャラクター一覧を確認する
VOICEVOXで利用できるキャラクターやスタイルは、以下のAPIで確認できます。
curl -s "http://$(ip route | grep default | awk '{print $3}'):50021/speakers" \
| jq -r '.[] | .name as $name | .styles[] | "\($name) (\(.style_name)): ID=\(.id)"'
気分転換に別のキャラクターへ変更したい場合は、表示されたIDを SPEAKER_JI_I の代わりに設定してください。