Skip to content

Latest commit

 

History

History
61 lines (40 loc) · 3 KB

File metadata and controls

61 lines (40 loc) · 3 KB

Claude Code integration

readback hooks into Claude Code via the Stop hook — a shell command that Claude Code fires every time an assistant turn finishes. The hook reads the most recent assistant text from the session transcript and pipes it through readback say for playback.

How it works

  1. You run readback install-hook once. This copies hooks/claude-code-stop.sh into ~/.claude/hooks/readback-stop-hook.sh and adds a Stop entry to ~/.claude/settings.json.
  2. From inside an active Claude Code chat, you run readback pin. This writes the current session id into ~/.config/readback/sessions.
  3. When Claude Code finishes responding, the hook fires for every session. It reads ~/.config/readback/sessions and only speaks if the current session id is in there. All other sessions exit silently.
  4. For pinned sessions, the hook walks the session's JSONL transcript in reverse, stops at the most recent user entry (end of current turn), collects text blocks from assistant entries in between (skipping tool calls and thinking blocks), and pipes the result to readback say, which invokes Kokoro and plays the audio.

Why pinning matters

Claude Code sessions are cheap — you probably have several open at any time. Without pinning, any Claude Code session would trigger audio, including ones running in background (build watchers, long-running agents, automated workflows). Pinning lets you say "only this one specific chat reads aloud."

Pin as many or as few as you want. The hook checks membership on every fire, so the overhead is near zero for unpinned sessions.

Race condition handling

Claude Code writes the transcript asynchronously — when the Stop hook fires, the latest assistant text may not yet be on disk. The hook retries up to 8 times (250ms apart, 2s total) waiting for the new text to appear. You can see this in the log:

grep 'extracted on attempt' /tmp/readback.log

Most turns resolve on attempt 1 or 2. If you see attempt 7 or 8 regularly, your filesystem is slow and you may want to bump the retry count in hooks/claude-code-stop.sh.

Debugging

# Tail the main log (hook + kokoro + CLI all write here)
tail -f /tmp/readback.log

# Run the doctor
readback doctor

# Inspect current pinning
readback list

# Test the hook manually with a synthetic payload
echo '{"session_id":"<session-id>","cwd":"/path/to/cwd","stop_hook_active":false}' \
    | ~/.claude/hooks/readback-stop-hook.sh

Uninstalling

readback uninstall-hook

This removes the Stop entry from ~/.claude/settings.json (preserving everything else) and deletes ~/.claude/hooks/readback-stop-hook.sh. Your session pins in ~/.config/readback/sessions are left alone — they're harmless without the hook registered.

Updating after a git pull

cd ~/workspace/readback
git pull
readback update-hook

The hook is a copy, not a symlink, so you have to explicitly refresh it. This is intentional — a symlink means a broken commit in the repo would instantly break your live hook.