# Getting AI Coding Agents to Talk: Voice Output for Claude Code, Desktop & Beyond

Praney Behl

January 2, 2026

8 min read

TLDR:AI coding agents support hooks or plugins that run after responses. Pipe the response text to a local TTS API (like Vois) and play the audio. Works with Claude Code, Claude Desktop, Codex CLI, and most extensible AI tools.

I spent last Tuesday staring at my terminal, debugging a particularly gnarly async race condition. Claude Code had just explained the fix—a beautiful, detailed explanation spanning maybe 400 words. But I'd already switched windows to implement it, and by the time I switched back, I'd forgotten half the details.

That's when it hit me: why am I reading all this? Couldn't Claude just... tell me?

## The Pattern: Surprisingly Simple

Here's the thing about AI coding assistants—they all generate text. And modern ones all have some form of extensibility. Hooks. Plugins. Skills. MCP servers. Whatever the terminology, they let you run code after the AI responds.

So the pattern is dead simple:

```
AI responds → Hook fires → Extract text → Send to TTS API → Play audio
```

That's it. Three moving parts. The AI doesn't even know it's happening. You just intercept the output, pass it to a text-to-speech engine, and pipe the audio to your speakers.

The only prerequisite? A local TTS server. Cloud APIs work too, but local means no latency, no API costs, and no sending your code discussions to third parties. If you're running [Vois](/content/site-root.html), you've already got an OpenAI-compatible TTS endpoint at `localhost:8080`.

Vois includes a built-in REST API server—just flip it on in Settings and you're ready to go.

## Quick Install (Claude Code)

Want to skip the explanation and just make it work? Here's the fast path:

```bash
# Clone the hook
git clone https://github.com/praneybehl/claude-code-voice-hook.git
cd claude-code-voice-hook

# Run the installer
./install.sh
```

That's it. The installer copies the hook script, updates your Claude Code settings, and you're done. Make sure Vois is running with its API server enabled, then start a new Claude Code session.

## Claude Code: How It Works

Claude Code uses a hook system. The `Stop` hook fires after Claude finishes responding—perfect for TTS. It's completely automatic: you don't ask Claude to speak, it just does.

The hook receives JSON via stdin with a path to the transcript file. A simple Bash script reads the transcript, finds the last assistant message, strips markdown formatting, sends it to the TTS API, and plays the audio.

Here's the script (pure Bash, no Python required):

````bash
#!/usr/bin/env bash
#
# Speak Claude's response using Vois TTS API.
# Requirements: jq, curl, afplay (macOS) or aplay (Linux)
#

set -euo pipefail

# Check if disabled
[[ "${VOIS_ENABLED:-1}" == "0" ]] && exit 0

# Check for jq
command -v jq &> /dev/null || exit 0

# Read hook input and extract transcript path
INPUT=$(cat)
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
[[ -z "$TRANSCRIPT_PATH" || ! -f "$TRANSCRIPT_PATH" ]] && exit 0

# Extract last assistant message
LAST_MESSAGE=$(jq -r '
    .messages | reverse
    | map(select(.role == "assistant")) | first
    | .content
    | if type == "string" then .
      elif type == "array" then
        [.[] | select(.type == "text") | .text] | join(" ")
      else "" end
' "$TRANSCRIPT_PATH" 2>/dev/null || echo "")

[[ -z "$LAST_MESSAGE" || ${#LAST_MESSAGE} -lt 20 ]] && exit 0

# Strip markdown (code blocks, formatting, links)
strip_markdown() {
    echo "$1" \
    | sed -E ':a;N;$!ba;s/```[^`]*```/ [code block] /g' \
    | sed -E 's/`[^`]+`//g' \
    | sed -E 's/\*\*([^*]+)\*\*/\1/g' \
    | sed -E 's/\[([^]]+)\]\([^)]+\)/\1/g' \
    | sed -E 's/^[[:space:]]*[-*+] //gm' \
    | sed -E 's/  +/ /g'
}

CLEAN_TEXT=$(strip_markdown "$LAST_MESSAGE")
[[ ${#CLEAN_TEXT} -lt 20 ]] && exit 0

# Truncate if too long
MAX_CHARS="${VOIS_MAX_CHARS:-1500}"
[[ ${#CLEAN_TEXT} -gt $MAX_CHARS ]] && CLEAN_TEXT="${CLEAN_TEXT:0:$MAX_CHARS}..."

# Configuration
API_URL="${VOIS_API_URL:-http://localhost:8080/v1/audio/speech}"
API_KEY="${VOIS_API_KEY:-}"
VOICE="${VOIS_VOICE:-af_heart}"

# Generate and play audio
TEMP_FILE=$(mktemp /tmp/vois-speech-XXXXXX.wav)
trap "rm -f $TEMP_FILE" EXIT

PAYLOAD=$(jq -n --arg input "$CLEAN_TEXT" --arg voice "$VOICE" \
    '{input: $input, voice: $voice, response_format: "wav"}')

HEADERS=(-H "Content-Type: application/json")
[[ -n "$API_KEY" ]] && HEADERS+=(-H "Authorization: Bearer $API_KEY")

curl -s -X POST "$API_URL" "${HEADERS[@]}" -d "$PAYLOAD" \
    -o "$TEMP_FILE" --max-time 120 2>/dev/null || exit 0

# Play audio (cross-platform)
case "$(uname -s)" in
    Darwin) afplay "$TEMP_FILE" 2>/dev/null ;;
    Linux)  aplay "$TEMP_FILE" 2>/dev/null || paplay "$TEMP_FILE" 2>/dev/null ;;
    MINGW*|MSYS*|CYGWIN*) powershell -Command "(New-Object Media.SoundPlayer '$TEMP_FILE').PlaySync()" ;;
esac
````

Save this to `~/.claude/hooks/speak-response.sh` and make it executable:

```bash
chmod +x ~/.claude/hooks/speak-response.sh
```

Then add the hook to your Claude Code settings (`~/.claude/settings.json`):

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/hooks/speak-response.sh",
            "timeout": 180
          }
        ]
      }
    ]
  }
}
```

Optionally set environment variables to customize:

```bash
export VOIS_API_URL="http://localhost:8080/v1/audio/speech"
export VOIS_API_KEY="your-api-key"  # Optional for local server
export VOIS_VOICE="af_heart"        # Pick your favorite voice
export VOIS_ENABLED="0"             # Temporarily disable
```

Click "API Docs" in Vois to get copy-paste curl commands, code examples in Python/JavaScript/Node.js, and the full list of 63 voices.

Now every time Claude Code finishes a response, you'll hear it. The text still shows up in your terminal instantly—the audio just follows a moment later.

## Claude Desktop: MCP Approach

Claude Desktop works differently. It uses MCP (Model Context Protocol) servers that expose tools Claude can call. Unlike hooks, this isn't automatic—Claude decides when to use the speak tool.

The upside? More control. You can ask Claude to speak only summaries, or only when you request it. The downside? You have to prompt Claude to use it, or set up a system prompt that instructs it to speak.

For most users who just want automatic speech, the hook approach (if using Claude Code) is simpler. But if you want Claude to make intelligent decisions about when to speak, MCP is the way.

## OpenAI Codex CLI

OpenAI's Codex CLI added a hooks system similar to Claude Code. If you're using Codex CLI, the pattern is nearly identical—create a hook that processes responses after generation.

The script works the same way: read the response, call your TTS server, play audio. Only the configuration file format differs.

## The Universal Pattern

Every AI tool that supports extensibility follows the same pattern. The specifics differ—hooks versus plugins versus skills versus MCP servers—but the underlying mechanism is consistent.

What you need:

1. **Post-response hook or event** — Some way to run code after the AI finishes generating
2. **Access to response text** — Either via file path, environment variable, or explicit argument
3. **Ability to run shell commands or HTTP requests** — To call your TTS server
4. **An audio player** — `afplay` on macOS, `aplay`/`paplay` on Linux, PowerShell on Windows (via Git Bash or WSL)

Tools like Cursor, Continue.dev, Aider, and custom agent frameworks all have extension points. Check their plugin docs. If they expose any lifecycle hooks, you can add voice output.

The TTS server is the universal integration point. OpenAI's API format has become a de facto standard—`POST /v1/audio/speech` with JSON body containing `input`, `voice`, and optionally `speed` and `response_format`. Any TTS server that speaks this protocol works with any client expecting it.

## Picking the Right Voice

Not all voices suit all contexts. A warm, conversational voice works great for explanations but sounds odd reading error messages. Consider:

**For code reviews and explanations**: Pick something natural and relaxed. Vois voices like `af_heart` or `bf_emma` have enough expression to keep long explanations engaging.

**For errors and warnings**: A crisper, more authoritative voice can help the message register. Try `am_adam` or `bm_george`.

**For quick confirmations**: Speed matters more than personality. Crank up the speed to 1.5x or 2x for short acknowledgments.

You can get creative—use environment variables to switch voices based on context, or modify the hook script to detect error messages and use a different voice. With 63 voices across 15 character categories in Vois, there's room to experiment.

## Going Further

Once you've got basic voice working, the possibilities open up.

**Conditional speaking**: Only speak errors, or only speak when the response exceeds a certain length. Filter out tool outputs and only voice the actual explanations.

**Bidirectional conversation**: Combine TTS with speech-to-text. Whisper runs locally, fast enough for real-time transcription. You could literally talk to your AI coding assistant.

**Multi-agent voices**: If you're running multiple AI agents, give each a distinct voice. Immediately know which agent is speaking without looking at the terminal.

**Summarization layer**: Before TTS, run the response through a smaller model that extracts key points. Speak the summary instead of the full response—perfect for long technical explanations you just need the gist of.

The hard part was always the TTS. Cloud APIs add latency and cost. Local TTS used to mean garbage quality. That's changed. Run a decent local TTS server, and suddenly voice interfaces for developer tools become practical.

Your AI coding assistant already understands your questions, reasons about your code, and generates solutions. Now it can tell you about them while you keep your eyes on the code.

## Frequently Asked Questions

### Does this work offline?

Yes, if you're using a local TTS server like Vois. The AI agent makes a request to localhost, and audio plays from your machine—no internet required for the voice generation.

### Can I use different voices for different tasks?

Absolutely. You can configure the voice via environment variables or modify the hook script to select voices based on context (code review vs chat vs error messages).

### Does this slow down responses?

The TTS runs after Claude finishes responding, so it doesn't block the text display. You see the response immediately, then hear it moments later.
