Feature request: AssistantMessageDelta hook for streaming-aware tooling (TTS, telemetry, mirror)
Motivation
The current hook surface fires only at boundary events: UserPromptSubmit, PreToolUse, PostToolUse, Stop, etc. There is no way to react to assistant text while it is being generated.
For an entire class of downstream tooling — local TTS, transcript mirroring, on-the-fly translation, accessibility narrators, telemetry — that boundary-only model means the user has to wait for the full response to finish before any side-effect can begin. On a 500-word response that is a 15–30s wait before any narration starts.
I just shipped a local Kokoro-TTS daemon (Null-Phnix/claude-voice#3) that drops warm time-to-first-audio from ~6s to ~0.6s. The bottleneck is no longer the model; it is that the Stop hook only fires after the assistant turn completes.
I verified by polling the JSONL transcript file every 100ms during an assistant response: the file grew exactly once, with the entire 7.2KB assistant message written in a single flush at end-of-turn. So tailing the transcript can't substitute either.
Proposed hook
A new hook event fired periodically during assistant generation with the in-progress text:
{
"event": "AssistantMessageDelta",
"session_id": "...",
"message_id": "...",
"delta": {
"text": "...the text added since the last delta..."
},
"cumulative_text": "...full assistant text so far..." // optional but very useful
}
Configuration would be the same as other hooks in ~/.claude/settings.json:
"hooks": {
"AssistantMessageDelta": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "...", "async": true, "timeout": 5 }] }
]
}
Implementation notes / requests
A few things would make this hook actually usable for streaming consumers:
async: trueshould be the only sensible mode. A sync hook on every delta would tank generation throughput. Document it clearly.- Debounce/batch. Firing per-token is overkill. Firing every ~100ms or on every sentence-terminator (
.!?followed by whitespace) would cover almost every consumer's needs without flooding hook processes. - Either
deltaorcumulative_text, not both required. Consumers can derive one from the other. Sending onlydeltais fine if you flag the order; sendingcumulative_textis fine if you accept the redundancy. - Final delta marker. A flag like
"is_final": trueon the last delta of a turn (or a separateAssistantMessageEndevent) helps consumers flush their last sentence cleanly.
Use cases this unlocks
- Real-time TTS — start speaking sentence 1 while Claude is still generating sentence 2. The reason I filed this.
- Accessibility — screen readers that narrate as the assistant types, the way visually-impaired users already use screen readers with chat apps.
- Live transcript mirror — push the response to a second device / dashboard as it's being typed, instead of post-hoc.
- Per-sentence translation — pipe deltas to a translator and emit in the user's language as fast as the assistant emits in English.
- Content moderation / safety filters — abort or rewrite a response mid-stream when an external rule fires, instead of after the user has already seen it.
- Token-level telemetry for tools that need finer-grained data than
Stopprovides.
Workarounds considered (and why they don't suffice)
- Tail the JSONL transcript — file is flushed once at end-of-turn (verified empirically). No streaming visibility.
- Wrap Claude Code's stdout via PTY — fragile, breaks every time the TUI redraws or cursor-moves, and forces users to launch via a wrapper.
PostToolUse— fires only on tool use, not on plain-text assistant turns.- Polling the API directly via a custom client — duplicates Claude Code's session, auth, MCP plumbing.
A first-class hook avoids all of those failure modes.
Happy to help
If a delta hook is on the roadmap, I'd be happy to:
- Test against any preview build and give feedback before stable.
- Update Null-Phnix/claude-voice as a reference consumer the moment it ships.
- Write user-facing docs once the contract is settled.
Thanks for building Claude Code.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗