[Feature request] Hook before an assistant response is displayed (PreResponse / blocking MessageDisplay)

Status Open
Maintainer reply None cached
Activity 1 comment · opened Sep 27, 2026

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 / PostToolBatch
additionalContext. 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 receives reason

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:

  1. make MessageDisplay blocking with a decision field;
  2. a Stop hook option holdDisplay: true that 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 accepts retry, so additionalContext is 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗