# I gave Claude Code a voice. Here’s what happened.

Claude Code says its summary out loud and takes your answer by voice. Hooks, a small macOS app and local Whisper: no API key, no extra bill.

- Author: Laszlo Csiki (https://www.linkedin.com/in/laszlo-csiki-7ba454108/)
- Published: 2026-09-29
- Kind: Video
- Demo: https://x2labs.ai/demos/claude-code-voice?embed=1 (macOS only · a recording, not a live demo)
- Page: https://x2labs.ai/notes/claude-code-voice

**TL;DR**

- Long Claude Code sessions are a lot of reading. I wanted to hear the summary and answer decisions by voice, hands-free.
- It runs on hooks, a shell library, a small native macOS app and a local Whisper model. No API key, no extra bill; it stays on the Claude subscription.
- The trick that keeps it free: Claude writes the sentence to be spoken itself, marked with a 🔊. No second model summarising anything.
- It's a Claude Code plugin now: one command to turn on, one to set up. We use it internally at X2 Mobile.

I have been living in Claude Code for months now; it's my daily driver. It works, but it reads like a firehose. Every turn is a wall of text, and most of the time the only thing I need from it is one sentence: did it finish, and does it need a decision from me. Reading for that one sentence gets old fast.

So I set three constraints and started building. Hands-free, because I want to look away from the screen. Faster than reading, or it's pointless. And free, of course! It had to run on my existing subscription, not on API tokens.

## The dead ends

Let me be honest, the first three ideas didn't survive the afternoon.

**The Agent SDK was the obvious route, and the first thing I dropped.** It gives you every event and a clean hook for user decisions. It also bills API usage, and you can't route a subscription through it. Constraint three, gone. Back to the CLI.

**The built-in dictation couldn't see my microphone.** macOS grants microphone access per app, and my terminal had never asked for it. Annoying at the time, but it planted the idea that fixed everything later: the microphone should belong to a small separate app, not to the terminal.

**The macOS Hungarian voice is a 2010 GPS.** Two minutes of listening and it was clear nobody would keep this on. Microsoft's neural voices through `edge-tts` fixed it. It's free, no key, a Hungarian voice that sounds like a person. The English one is a calm British Ryan.

**Post-correcting transcripts with Haiku worked, but took ten seconds.** A headless Claude process boots for each call. Correct, but too slow for a push-to-talk loop; it's off by default now, and a vocabulary hint to Whisper does most of the job for free.

## How it fits together

Four pieces, no framework.

**Hooks.** Claude Code fires an event for everything that matters: a reply finished, a question is about to be asked, a permission is about to be requested, the user started typing. Each hook is a shell script.

**A shell library** does the speaking. It strips markdown, rewrites English jargon so the Hungarian voice doesn't butcher it ("pull request" becomes "pul rikveszt"), splits the text into sentences, synthesises them in parallel and plays them in order. A queue makes sure two sessions never talk over each other; only I can interrupt.

**A native Swift app** owns everything that needs OS permissions: the microphone, a global push-to-talk key (hold Option+Space), a Siri-style orb in the corner that turns purple when Claude talks and blue-green when it listens, and a menu bar icon with the switches. It also notices when Zoom or Teams is in a call and stays quiet, then tells me what I missed when the call ends.

![The floating voice orb, glowing and shifting colour](https://x2labs.ai/img/orb.gif)

Purple when Claude talks. Blue-green when it listens.

![The macOS menu bar, the voice app’s icon moving like a sound wave](https://x2labs.ai/img/menubar.gif)

Menu bar state while Claude is speaking.

**Local Whisper** (`mlx-whisper` on Apple Silicon) turns my voice into text in about 2–3 seconds. Nothing leaves the machine, offline.

> When voice mode is on, Claude ends every reply with one line starting with 🔊: the spoken summary, written by the model that already knows what matters. The hook just reads that line aloud. No summariser, no second call, no latency.

The hands-free loop is the same idea in reverse. Claude asks a question, the hook reads it out, the app records until I stop talking, Whisper transcribes, and the answer goes back to the session that asked, not to whatever window happens to be focused. "Firestore", "the second one" or "let's use MongoDB instead" all work.

## What it does today

| Area      | Feature                                                                                                              |
| --------- | -------------------------------------------------------------------------------------------------------------------- |
| Output    | Spoken summary of every reply, and of the "on it, first I will…" acknowledgement before tool calls                   |
| Decisions | Questions and permission prompts read aloud, answered by voice, routed to the asking session                         |
| Input     | Push-to-talk anywhere, local Whisper, typed into the active window                                                   |
| Awareness | Session name announced when several sessions talk, silent during Zoom/Teams calls, missed-messages digest afterwards |
| Language  | Hungarian and English, auto-detected from what I say or type, each with its own voice and pronunciation dictionary   |
| UI        | Floating orb, animated menu bar icon, one `/talk` command for everything                                             |

## My take

Latency decides whether you keep it on. The first version waited 8–10 seconds before speaking; I was already reading the screen by then. Sentence-by-sentence synthesis and a cache for fixed phrases brought the first word down to about a second. Anything slower and the whole thing is decoration.

A voice UI plays by different rules than a text UI. It must never repeat itself, never cut itself off, and always say who is talking when more than one session is alive. I shipped each of those bugs and then fixed them; the "never interrupt yourself" one took a proper queue.

Short mixed sentences flip language detection. "ok, manual mode on, mehet" read as English, and the next permission prompt came in English with "Yes, No" options. A confidence threshold and a minimum length solved it, but I'd have never guessed that one upfront.

> And the big one: own the microphone. Once the small app held the permission, every terminal quirk disappeared.

Don't get me wrong, it's not a product yet. It's macOS only, the wake word is a hotkey, the regression tests are shell scripts I run by hand with recorded audio, and Warp won't let me focus the right tab on push-to-talk (iTerm2 would maybe). But it's the first version of a tool I turned on in the morning and didn't turn off.

First published on [X](https://x.com/x2labs_ai/status/2104800490378629486) · 29 Sep 2026Written by Laszlo Csiki
