[Feature request] Hook before an assistant response is displayed (PreResponse / blocking MessageDisplay)
Problem
Stop hooks can reject a finished response (exit 2 / decision: block) and make the model rewrite it,
but they run after the response has already been rendered in the terminal. The user reads the
rejected version first, then the hook message, then the corrected version. The rejected text cannot be
retracted or hidden.
In our setup (a heavily hooked Claude Code environment, v2.1.283) quality gates enforce things like
"after an auto-mode classifier denial, show this exact block instead of a ! command" or "don't
deliver a vertical video whose captions sit in the band Facebook's feed crops".
Measured over 14 days (336 sessions, 1,982 turns):
- 76 turns (3.8%) were bounced by a Stop gate, 86 bounces in total;
- the user saw ~19.5k words of rejected response text plus ~3.3k words of gate messages;
- one example: 186 superfluous words on screen before the final 16-word answer (two consecutive bounces).
About half the bounces (43/86) had a trigger known before the model started writing (a tool result, a
file just written). We now inject those requirements via PostToolUse / PostToolBatchadditionalContext. The other half is about the prose itself: dead links, missing link form,
a question asked in two channels at once. Those can only be judged on the final text, and today
only after the user has seen it.
Request
An event that fires when the final assistant text of a turn is complete, before it is displayed,
with the same blocking semantics as Stop:
- input:
session_id,transcript_path, the response text (last_assistant_message); - output:
decision: "block"+reason→ the response is not shown, the model receivesreason
and regenerates; the user only sees the accepted version (optionally a one-line "revising…" status);
- a cap on consecutive blocks (like
stop_hook_active) to prevent loops.
Alternatives that would also work:
- make
MessageDisplayblocking with adecisionfield; - a
Stophook optionholdDisplay: truethat buffers the final text until Stop hooks resolve.
Why existing mechanisms don't cover it
Stop/SubagentStop: run after render.PermissionDenied: its output schema only acceptsretry, soadditionalContextis silently dropped.PostToolUse/PostToolBatch: fine for triggers that come from tools, but they can't see the prose
the model is about to write.
Cost of buffering
Streaming latency applies only to the final text block of a turn, and only when a hook for this event
is registered. Users without such hooks see no difference.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗