DEV Community

Kelly
Kelly

Posted on

Make Claude Code talk back with a text-to-speech Stop hook

Disclosure: I'm Kelly, and I work on Heard (heard.dev), a voice layer for coding agents. Most of this post is the free, do-it-yourself way, because for one session that is all you need. Heard comes up near the end as one option.

The Claude Code CLI listens but does not speak. Its /voice command is dictation: you talk and it types your prompt. The CLI has no spoken output, so if you want to hear when a task is done, you wire it up yourself. It takes one hook.

The Stop hook

The Stop event fires when Claude finishes responding. Its JSON input on stdin includes last_assistant_message, the text of Claude's final reply. Pipe that into macOS say. Add this to ~/.claude/settings.json:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.last_assistant_message // empty' | head -c 600 | say",
            "async": true
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

You need jq (brew install jq). The field name and hook shape are checked against the Stop section of the Claude Code hooks reference (October 2026).

Two details matter:

  • head -c 600 caps the length. Final replies can be long reports, and say reads every word.
  • "async": true runs the hook in the background, so Claude Code carries on while the voice is still talking.

On Linux, replace say with spd-say -e from the speech-dispatcher package.

If you only want a sound, not the words, use afplay /System/Library/Sounds/Glass.aiff as the command instead.

Three small upgrades

  • Say which session finished. Every hook input includes cwd, so jq -r .cwd | xargs basename | say speaks the folder name.
  • Hear failures. Stop does not fire when a turn ends on an API error. Claude Code fires StopFailure instead, so add the same command there.
  • Debug a silent hook. Run /hooks inside Claude Code to see what loaded. A JSON syntax error in settings.json is the usual cause.

The same idea in Codex

Codex has a notify setting in ~/.codex/config.toml. It runs a command with one JSON argument when a turn completes, with type set to agent-turn-complete and the reply in last-assistant-message:

#!/usr/bin/env python3
import json, subprocess, sys

event = json.loads(sys.argv[1])
if event.get("type") == "agent-turn-complete":
    text = (event.get("last-assistant-message") or "Codex finished.")[:300]
    subprocess.Popen(["say", text])
Enter fullscreen mode Exit fullscreen mode

Save it as ~/.codex/speak.py, then point Codex at it with its absolute path:

notify = ["python3", "/Users/you/.codex/speak.py"]
Enter fullscreen mode Exit fullscreen mode

subprocess.Popen returns straight away, so Codex is not blocked while it speaks.

When several sessions get noisy

For one session, stop here. The hook is free and local. The trouble starts with three or four terminals:

  • It reads, it does not summarise. say reads markdown, file paths and code exactly as written. A reply full of paths is hard to follow by ear.
  • Every event sounds the same. "Done", "tests failed" and "waiting for approval" arrive as one voice reading text.
  • Sessions talk over each other, and "finished" does not tell you which one needs you.

A few tools go further. Each description comes from the project's own README, read this week:

  • Aloud (softcane/aloud, MIT) speaks a short gist of each Claude Code and Codex reply on your Mac (about 240 characters by default) with local Kokoro speech, and aloud full reads the whole reply. You turn it on per session with aloud on; questions take priority over completion messages and routine tool calls stay silent. It wins if you want something free, local and per-session. It needs Python 3.11 or 3.12 and Homebrew.
  • VoiceMode (mbailey/voicemode, MIT) is for spoken back-and-forth conversations with Claude Code. It can run locally with Whisper and Kokoro or use OpenAI's cloud, and it runs on Linux, macOS and Windows. It wins if you want to talk with one session, or you are not on a Mac.
  • Heard (heard.dev) is the one I work on, a Mac app (macOS 14 or later) for Claude Code and Codex. It speaks a sentence or two about what changed rather than the reply itself. Questions, blockers and failures come through first, and routine progress from parallel sessions is batched into short summaries that start with the project name. Focus mode keeps routine progress quiet and speaks only for questions, blockers and failures. On the Power plan you can answer back by voice. The engine is open source under Apache 2.0 with a local Kokoro voice and no account needed; the downloadable app is a managed build with a 14-day Power trial at signup, no card required. Where the others win: Heard is Mac only, and after the 14-day trial the app needs a paid plan (Pro or Power, on heard.dev/pricing); the free option is building the open-source engine from source.

There is a longer side-by-side of every Claude Code text-to-speech option on heard.dev, and a step-by-step sound-when-done guide with the hook variations.

Which one?

  • One session, you just want to know it is done: the Stop hook or the Codex notify script above.
  • Free, local speech in the sessions you choose, on a Mac: Aloud.
  • A spoken conversation with Claude, on any OS: VoiceMode.
  • Several Claude Code and Codex sessions, with short summaries and replies by voice: Heard.

If you build your own variation of the hook, I'd like to see it in the comments.

Top comments (0)