Adding Text-to-Speech to Claude Code Notifications with Kokoro and Hooks
Ever wished your AI coding assistant could literally speak to you? Today we'll walk through setting up Claude Code to announce its notifications using text-to-speech, making your coding workflow more accessible and hands-free.
What We're Building
Claude Code has a powerful feature called "hooks" that let you run custom commands at specific points in its workflow. We'll use this to:
- Catch notification events from Claude Code
- Send the notification text to Kokoro (a high-quality TTS engine)
- Play the audio through your speakers
The result? Instead of just seeing notifications, you'll hear Claude Code announce things like "Task completed successfully" or "Waiting for your permission to run this command."
Prerequisites
Before we start, make sure you have:
- Claude Code installed and working
- Kokoro-FastAPI running locally (GitHub repo)
- uv package manager installed (installation guide)
- A MacBook Pro (though this works on other systems with minor tweaks)
Step 1: Set Up Your Python Environment
We'll use uv
to manage our Python dependencies. This is cleaner than installing packages globally and avoids version conflicts.
In your home directory, create a new Python project:
cd ~
uv init claude-tts-hooks
cd claude-tts-hooks
Add the required packages:
uv add openai pyaudio
This creates a virtual environment and installs:
openai
: For communicating with Kokoro-FastAPI (which uses OpenAI-compatible endpoints)pyaudio
: For playing audio directly to your speakers
Step 2: Create the Hook Script
Create a file called kokoro_tts_hook.py
in your project directory:
#!/usr/bin/env python3
"""
Claude Code Notification Hook for Kokoro TTS
Sends notification text to Kokoro-FastAPI for text-to-speech conversion
"""
import json
import sys
import os
import tempfile
from pathlib import Path
try:
from openai import OpenAI
import pyaudio
except ImportError as e:
print(f"Missing required packages: {e}", file=sys.stderr)
print("Install with: uv add openai pyaudio", file=sys.stderr)
sys.exit(1)
# Configuration - Customize these for your setup
KOKORO_BASE_URL = "http://localhost:8880/v1"
KOKORO_VOICE = "af_bella" # Try: af_bella, af_sarah, am_adam, etc.
AUDIO_OUTPUT_METHOD = "speakers" # "speakers" or "file"
AUDIO_OUTPUT_DIR = os.path.expanduser("~/claude_notifications")
def play_to_speakers(client, text):
"""Stream audio directly to speakers using PyAudio"""
try:
# Set up audio output
player = pyaudio.PyAudio().open(
format=pyaudio.paInt16,
channels=1,
rate=24000,
output=True
)
# Stream TTS audio in real-time
with client.audio.speech.with_streaming_response.create(
model="kokoro",
voice=KOKORO_VOICE,
response_format="pcm", # Raw audio format
input=text
) as response:
# Play audio chunks as they arrive
for chunk in response.iter_bytes(chunk_size=1024):
player.write(chunk)
player.stop_stream()
player.close()
return True
except Exception as e:
print(f"Error playing to speakers: {e}", file=sys.stderr)
return False
def clean_notification_text(text):
"""Clean and prepare notification text for better TTS"""
import re
# Remove markdown formatting
text = re.sub(r'[*_`]', '', text)
# Keep it reasonable length
max_length = 200
if len(text) > max_length:
text = text[:max_length] + "..."
# Make tech terms sound better
replacements = {
"Claude Code": "Claude Code",
"TTS": "text to speech",
"API": "A P I",
"JSON": "jay-son",
"HTTP": "H T T P"
}
for old, new in replacements.items():
text = text.replace(old, new)
return text.strip()
def main():
try:
# Read the notification data from Claude Code
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
# Extract the notification message
message = input_data.get("message", "")
title = input_data.get("title", "Claude Code")
if not message:
sys.exit(1) # Nothing to say
# Prepare the text for TTS
if title and title != "Claude Code":
tts_text = f"{title}: {message}"
else:
tts_text = message
tts_text = clean_notification_text(tts_text)
# Connect to Kokoro
try:
client = OpenAI(
base_url=KOKORO_BASE_URL,
api_key="not-needed" # Kokoro doesn't require real auth
)
except Exception as e:
print(f"Error connecting to Kokoro: {e}", file=sys.stderr)
sys.exit(1)
# Generate and play the speech
success = play_to_speakers(client, tts_text)
if success:
# Tell Claude Code we succeeded and don't clutter the transcript
output = {
"continue": True,
"suppressOutput": True
}
print(json.dumps(output))
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
Make the script executable:
chmod +x kokoro_tts_hook.py
Step 3: Configure Claude Code Hooks
Now we need to tell Claude Code to run our script when notifications happen. There are two ways to do this:
Option A: Using the Claude Code Interface (Recommended)
- Open Claude Code and run the
/hooks
command - Select the
Notification
event - For the matcher, leave it empty (this catches all notifications)
- Choose "User settings" to apply this to all your projects
- Press Esc to save and exit
Add a new hook with this command:
uv run /Users/yourusername/claude-tts-hooks/kokoro_tts_hook.py
(Replace yourusername
with your actual username)
Option B: Manual Configuration
Edit your ~/.claude/settings.json
file and add this configuration:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "uv run /Users/yourusername/claude-tts-hooks/kokoro_tts_hook.py"
}
]
}
]
}
}
Step 4: Start Kokoro-FastAPI
Make sure your Kokoro-FastAPI server is running. If you haven't set it up yet, follow the instructions in the Kokoro-FastAPI repository.
Typically, you'll start it with:
python main.py
The server should be accessible at http://localhost:8880
.
Step 5: Test Your Setup
Let's test if everything works:
- Open Claude Code in a new project
- When Claude asks for permission, you should hear the notification spoken aloud!
Ask Claude to run a command that requires permission, like:
Create a simple Python script that prints "Hello World"
Customization Options
Changing Voices
Kokoro supports different voices. Edit the KOKORO_VOICE
variable in your script:
KOKORO_VOICE = "af_sarah" # Try: af_bella, af_sarah, am_adam, etc.
Saving Audio Files Instead
If you prefer to save notifications as audio files rather than playing them immediately:
AUDIO_OUTPUT_METHOD = "file"
AUDIO_OUTPUT_DIR = os.path.expanduser("~/Desktop/claude_audio")
Adjusting the Kokoro Server URL
If your Kokoro server runs on a different port or machine:
KOKORO_BASE_URL = "http://localhost:8881/v1" # Different port
# or
KOKORO_BASE_URL = "http://192.168.1.100:8880/v1" # Different machine
How It Works
Here's what happens behind the scenes:
- Claude Code generates a notification (like "Task completed" or "Permission required")
- The hook system activates and runs our Python script
- Our script receives notification data as JSON via stdin
- Text gets cleaned and prepared for better speech synthesis
- The script calls Kokoro-FastAPI using the OpenAI-compatible interface
- Audio streams directly to your speakers in real-time
The beauty of using hooks is that this happens automatically—no manual intervention required.
Troubleshooting
"Module not found" errors
Make sure you're in the right directory and have run uv add openai pyaudio
. The uv run
command should handle the virtual environment automatically.
No audio output
- Check that Kokoro-FastAPI is running and accessible
- Verify your system audio settings
- Try changing the voice or audio format
Hook not triggering
- Run
/hooks
in Claude Code to verify your configuration - Check that the script path is correct and absolute
- Look for error messages in Claude Code's transcript mode (Ctrl-R)
PyAudio installation issues on macOS
If PyAudio fails to install, you might need:
brew install portaudio
Why This Matters
Adding TTS to Claude Code notifications creates a more accessible and multitasking-friendly development environment. You can:
- Stay focused on your code while knowing what Claude is doing
- Work hands-free during certain workflows
- Catch important notifications even when not looking at the screen
- Improve accessibility for visually impaired developers
Going Further
This is just the beginning! You could extend this setup to:
- Filter notifications by type (only speak errors, not successes)
- Use different voices for different events (error voice vs. success voice)
- Add sound effects before or after the speech
- Integrate with other TTS engines like ElevenLabs or Azure Speech
- Create hooks for other events like file saves or command completions
The hook system in Claude Code is incredibly powerful, and TTS integration is just one example of how you can customize your AI-assisted development workflow.
Conclusion
By combining Claude Code's hooks, Kokoro's high-quality TTS, and uv's clean dependency management, we've created a seamless audio notification system that makes AI-assisted coding more accessible and engaging.
The setup might seem complex at first, but once it's working, you'll wonder how you ever coded without your AI assistant speaking to you. Give it a try and let the future of voice-enabled development begin!
Have questions or improvements? The Claude Code hooks system is incredibly flexible, and there are many ways to customize this setup for your specific workflow.