Teaching Claude Code to tell me when it's done

I usually have four or five Claude Code sessions running in tmux, one per client site. The slow part of my day isn't the agents. It's me noticing that one of them finished, or has been sitting on a permission prompt for ten minutes while I looked at something else.

So I gave them a voice.

Reading the whole reply is unbearable

Claude Code runs a Stop hook after every turn, and the hook gets the final message on stdin. Piping that straight into text-to-speech works for about five minutes. Then it reads you a file path, a diff summary and a bulleted list, and you rip it out.

What you want to hear is one or two sentences: what got done, or what it needs from you.

Summarize first, then speak

My first try asked the model to end every reply with a line starting SPOKEN: and the hook read just that line. It worked, but the line showed up on screen in every reply, and I couldn't hide it.

Now the hook sends the reply to a small, cheap model and speaks what comes back. A few details mattered more than I expected:

Several sessions at once

With five sessions, two of them sometimes finish at the same moment and talk over each other, and a generic "Claude needs your permission" doesn't say which one.

The voice

The built-in macOS voices were the weakest part. I tried a few open models locally on Apple Silicon. Kokoro, an 82 million parameter model, sounded far better than the system voices and renders a sentence in about a third of a second when kept warm in a small local server. A 4 billion parameter model sounded a little more natural but took over three seconds per summary, which is too slow for a notification.

What I'd tell someone trying this

Most of the value came from two decisions, not from the voice: what's worth interrupting me for, and saying which session it came from. A plain system voice would have been fine once those were right.