Hear your AI coding agent's replies in a Speechify voice, with the words highlighted as they are spoken.
Claude Code works through a task. Readback reads the notes Claude writes between tool calls as they land, then condenses the finished reply to a few spoken sentences, all in the VS Code sidebar with each word lit as it is said. The full reply is one click away. Click a word to jump to it. Step by sentence. Cycle the speed. Pick any voice your Speechify key can render, including your own clone, and hear a sample of each before you choose.
Paste this into Claude Code:
Install the Readback VS Code extension, then run its "Install the Claude Code hook" command and tell me when it is done.
Or by hand: install the extension, then either answer the prompt that appears on first start, click Readback in the status bar, or run Readback: Set Speechify API key from the Command Palette. Then press Install the hook in the Readback view.
You need a Speechify API key. Create one at https://platform.speechify.ai/api-keys?utm_source=github&utm_medium=readme&utm_campaign=readback. Readback keeps it in VS Code's secret storage and every reply you listen to bills your own workspace.
After a reload, click once anywhere in the VS Code window before you expect to hear anything. The browser inside VS Code keeps audio silent until then, and the player shows a card until it has played once. Replies that arrive before that queue up.
Readback is also a Claude Code plugin: a band above the prompt shows the
reply and lights each word as it is spoken. It needs Claude Code 2.1.275 or
later, in the terminal or the desktop app. In T3 Code and other hosts that
draw no band, /readback play and the other words below take the keys'
place.
/plugin marketplace add Speechify-AI/readback
/plugin install readback@readback
From a shell, claude plugin install readback --marketplace Speechify-AI/readback adds the marketplace and installs in one step.
The install dialog asks for your Speechify API key. Create one for free at
https://platform.speechify.ai/api-keys?utm_source=github&utm_medium=readme&utm_campaign=readback.
The key is stored as a sensitive plugin setting and billed to your own
workspace; change it any time in /plugin.
/readback onand/readback offturn Readback on or off; bare/readbackprints the status (on or off, voice, speed, what is playing). Turning on checks the key against the live API. Nothing plays or bills until you press play. On or off is read when a session starts, so turning it off in one terminal leaves sessions already open on until they restart./readback play,pause,resume,stopandfulldo what the band's buttons do, for a host that draws no band (T3 Code and other SDK hosts); each word does what it says and nothing else. There a new session sleeps until/readbackis typed once, since nothing tells it from a script's headless run, so its first reply is not heard. Withauto onit starts at once instead, and so does any headlessclaude -prun on the machine while autoplay stays on./readback againplays the last reply from its start. The render is cached, so a replay costs nothing./readback auto onplays each reply as it lands, as if you had pressed play; off by default, and every reply then renders and bills.- Typing the next prompt stops whatever is playing, as in the extension.
- With autoplay on, the notes Claude writes between tool calls are read as they land, as the extension reads them; the finished reply follows. Stop (2) during a turn keeps its later notes quiet; the reply still plays.
- One alert is spoken, on by default: a permission prompt that has waited
a few seconds for you ("Claude is waiting for your permission to run npm
install").
/readback alerts offsilences it. It is one short render per tool call, cached after the first time. /readback voiceopens a voice picker in the band: a search field and the matching voices, featured first. The band lists 64 at a time; type to narrow./readback voice harper(a name, language or tag) picks the first match by word, where no band draws. The choice is this project's;/readback voice default harpersets the voice for projects without one, and/readback voice resetdrops this project's./readback voice randomdraws a voice in your language that no project uses yet, featured first; run it again for another. A voice per project tells sessions apart by ear.- Each reply opens with the project's folder name, as in the extension, so you know which session is speaking.
/readback speed 1.25sets the speed (0.75 to 2), and the band's speed button steps through the same values. Speechify renders the rate into the audio, so a changed speed re-renders: what was loaded is dropped and 1 plays the turn again at the new rate.- Keys 1, 2 and 3 play or pause, stop, and play the full reply. They work only while the prompt is empty: a "1" typed as the first character of a message goes to Readback, not to Claude.
- While Readback is on, the plugin adds a short section to the system
prompt asking Claude to end each long reply with a spoken note, an HTML
comment holding one to three sentences for the ear. That note is what is
read, at once, with no second run. A long reply without one is condensed
with
claude -pon your own Claude subscription when the turn ends. The full reply is one press of 3 away either way. Condensing needs theclaudeCLI and has been checked on macOS only; on Windows a reply without a note is read in full. - Several Claude Code sessions share one speaker. Pressing play in one pauses another, which resumes once the first is done. A reply that lands while another session is speaking waits its turn instead of cutting in; the band says "waiting for another session to finish" meanwhile.
- Renders are cached under
~/.readback/claude-code/renders, so replaying costs nothing.
The VS Code extension and the plugin are independent; use either or both. With both, a reply is read once: the plugin stays quiet when a VS Code window with that project open is listening, and reads as usual when VS Code is closed or open on another project.
- Five hooks in
~/.claude/settings.json,MessageDisplay,PreToolUse,Notification,UserPromptSubmitandStop, post each assistant message, each tool call, each permission prompt left waiting, each prompt of yours and each finished turn to Readback. The hook is ten lines of POSIX shell and curl (the same lines in PowerShell on Windows). It needs no runtime on your PATH and never decides anything itself. - Codex CLI can be a second source: a
Stophook in~/.codex/hooks.jsonposts each finished turn the same way. Codex has no per-message hook, so its notes between tool calls are not read. See Codex below. - A message followed by a tool call was a note written before it, and
Readback reads it as it stands while the tool runs. A message followed by
Stop is the finished reply and gets condensed, unless it is under 500
characters already, which is read as it stands. Turn the notes off with
readback.progress. - A permission prompt that has waited about six seconds (Claude Code's own
threshold for the
permission_promptnotification) is read at once, ahead of whatever is playing: "Claude is waiting for your permission to run npm install, in readback." The tool call comes from thePreToolUsethat preceded it. Whatever was playing carries on afterwards. Turn it off withreadback.alerts. - When you submit your next prompt, Readback stops reading: the reply it was on has been read. A reply still being condensed when you type lands in the list without playing.
- Each turn opens with a one-word lead-in, the project's name, that renders once and plays from the cache, so you hear that a turn has started within a few milliseconds while its first paragraph renders. The time is shown next to it, not spoken.
- Readback condenses the reply with
claude -pon Sonnet, on your own Claude subscription, so it holds no model key. The run loads no settings and is marked so the hook ignores it, which is what stops a reply about a reply. Ifclaudeis missing or the run fails, you hear the full reply instead. Turn it off withreadback.summarize. - Readback flattens the text to plain prose (code blocks become "code
omitted", a table of up to four rows its header and value pairs, a longer
one a note, links their text), rewrites the few things a voice gets
wrong (a bare URL is "a link",
app.ts:42is "app.ts, line 42", a commit hash its first four characters,v0.9.0"version 0.9.0",>=and the like as words), splits it into paragraphs, and synthesizes each one through/v1/audio/speechas the player reaches it. What is shown is the same text. - A reply that ends with its own spoken note (an HTML comment the
Readback plugin asks Claude for while it is on,
<!-- readback: ... -->) is read from that note; no condensing run is made for it. The comment is never shown. - Speech marks come back with character offsets into the exact text sent, and that is what drives the highlight. No guessing, no token alignment.
- Renders are cached on disk by text, voice and model. Replaying is free. Changing voice re-renders.
| Command | What it does |
|---|---|
| Readback: Set Speechify API key | Checks the key against the live API before storing it |
| Readback: Choose voice | Opens the picker in the panel: featured voices first, then every voice grouped by language, with search, filters by gender, clone or tag, and a sample of each. Saved for the open project; set readback.voice in user settings for the default |
| Readback: Install the Claude Code hook | Adds Stop, MessageDisplay, PreToolUse, Notification and UserPromptSubmit hooks, leaving the rest of the file alone |
| Readback: Remove the Claude Code hook | Removes exactly that hook |
| Readback: Install the Codex hook | Adds a Stop hook to ~/.codex/hooks.json. Also a row in the settings panel when a ~/.codex folder exists |
| Readback: Remove the Codex hook | Removes exactly that hook |
| Readback: Catch me up | One briefing of every listed reply you have not heard. The history button in the bar does the same |
| Readback: Read selection | Reads the editor selection. Also in the editor context menu |
| Readback: Stop | Stops playback and clears the queue |
| Readback: Clear the list | Forgets every listed reply. The trash button in the bar does the same |
| Setting | Default | Meaning |
|---|---|---|
readback.autoplay |
true |
Play each new reply as it arrives; off lists them and waits for play |
readback.summarize |
claude |
Condense replies with claude -p, or off to read them in full |
readback.progress |
true |
Read the notes Claude writes between tool calls as they arrive |
readback.alerts |
true |
Say when Claude has been waiting for your permission for a few seconds, ahead of whatever is playing |
readback.voice |
harper_32 |
Speechify voice id. Per project when set from the picker. Unset, and Claude Code's language is one the model speaks, a voice in that language is used |
readback.model |
simba-3.5 |
Speechify model. On an English-only model (simba-3.2) other voices render on simba-3.0 instead |
readback.speed |
1 |
Playback rate. Per project when cycled from the player |
readback.minChars |
80 |
Replies shorter than this are skipped |
readback.maxChars |
4000 |
Longer replies are cut at a sentence and end with "and more" |
Readback speaks whatever language Claude writes in, as long as the voice
speaks it too. On simba-3.5, the default, every voice speaks its 48
languages. On simba-3.2, which is English only, a voice in another
language renders on simba-3.0, which speaks German, Spanish (Spain and
Mexico), French, Italian and Brazilian Portuguese. The voice's locale goes
with every request.
To have Claude answer in Spanish, add language to
~/.claude/settings.json, or to a project's .claude/settings.json for
that project only:
{
"language": "spanish"
}Claude Code applies it straight away. Readback reads the same setting:
- If you have not picked a voice, it uses one in that language.
- If you have, it keeps yours. The voice picker lists that language first and says when your voice speaks a different one. Pick a voice from that group.
The condensed summary is written in the reply's language, so it matches what Claude wrote. The catch-up lead-in ("Catching up on 3 replies") is still English. If Claude writes in a language no voice speaks, such as Japanese, the reply is read by your voice anyway and will not sound right.
Each window shows and speaks only its own project's turns. A turn belongs to a window when the folder Claude Code ran in is inside one of its workspace folders, or contains one (running Claude at a monorepo's root while the window has a sub-project open is the same project). A turn that matches no open window is not read.
A reply counts as heard when it started playing while this VS Code window
was focused. One that played to an empty room, or that waited because
autoplay was off, stays unheard. Catch me up (the history button, or
the command) takes every unheard reply in the list, oldest first, and has
claude -p brief them together: what landed, what is still open, what
waits on you, grouped by project. The briefing is listed as its own turn
and plays; the replies it covers stay in the list and count as heard.
Without claude, or with readback.summarize off, the same replies are
read one after another instead. At most 20 are read in detail; older ones
are counted in the lead-in.
Open the settings panel and press Install on the Codex CLI row, or run
Readback: Install the Codex hook. This writes one Stop entry to
~/.codex/hooks.json, next to whatever is already there. Codex runs a new
hook only after you have trusted it once: open Codex, run /hooks, and
approve Readback. Until then nothing arrives. Condensing still runs
through claude -p when it is installed; without it Codex replies are read
in full.
Play, pause, back and forward a sentence, stop. Space, left and right do the same when the player has focus. The progress line tracks the current paragraph. The autoplay toggle decides whether a new reply plays at once or waits, marked with a dot, for you to press play. The speed pill cycles from 0.75× to 2×; the voice pill opens the voice picker. Each turn has its own play button, and a condensed turn has a "Full reply" section with one of its own.
- Claude Code and Codex CLI are the sources. The Claude Code hook fires for sessions in a terminal and in the Claude Code panel in VS Code.
- On Windows the hook is a PowerShell script run through
powershell.exewith no shell in between. It has not yet been tried on a Windows machine by us; if you run it there, an issue with what you saw would help. - Read-along needs speech marks, which the streaming endpoint does not return, so replies are read paragraph by paragraph at up to 2,000 characters per request.
npm install
npm run typecheck && npm test
npm run build # dist/extension.js
npm run package # readback-<version>.vsixPress F5 in VS Code to run the extension in a development host.
The Claude Code plugin is .claude-plugin/, hooks/ and mod/. Try it
with claude --plugin-dir . and check it with claude plugin validate .,
which npm test runs when claude is on your PATH. It ships from a tree
of its own, without the extension's package.json and lockfile:
node scripts/plugin-tree.mjs --out <dir> writes it, and --commit
commits it to the plugin branch and tags it readback--v<version>.
MIT. Built by Speechify. The player's icons are VS Code's codicons (CC BY 4.0).