Blocking Stop hook reprints the entire corrected answer on top of the original (no pre-emit event / no way to retract streamed output)
What happened
A blocking Stop hook that returns {"decision":"block","reason":"..."} fires after the assistant's reply has already fully streamed to the terminal. The block then makes the model regenerate a corrected reply, which streams as a second message. The user therefore sees the entire answer twice: the original (rejected) draft, then the corrected rewrite on top of it. For long answers this is a lot of duplicated scrollback on every block.
There is no way to fix this from the hook side, because:
- The earliest hook event that can inspect/reject the assistant's reply is
Stop, which is post-display. (Confirmed against the 2.1.220 bundle — the hook event set has no pre-response/pre-emit event.) - A
Stopblock result carries no field to retract, clear, or replace the already-streamed message.suppressOutputonly governs the hook's own success stdout, not the assistant message.
Repro
- Register a
Stophook that inspects the last assistant message and returns{"decision":"block","reason":"rewrite it this way..."}when it dislikes something (e.g. a lint/style rule). - Ask a question that produces a long answer containing the disliked pattern.
- Observe: the full answer streams, the hook blocks, and the full corrected answer streams again below it. Both remain in scrollback.
Feature request
Give hook authors a way to correct a reply without the user seeing the rejected draft. Either of these would close it:
- A pre-emit /
PreResponsehook event that fires after the model finishes generating but before the text is shown, supportingdecision: "block". The guard inspects the drafted reply, rejects it, and triggers a regenerate — so only the corrected answer is ever displayed. (ExistingStopguard bodies would move to this event unchanged.) - OR a field on the
Stopblock result — e.g.replaceOutput: true/retractDraft: true— instructing the harness to clear the just-streamed reply and show only the regenerated one.
Related, smaller display bug
Separately, a single decision:block from a Stop/PostToolUse hook renders its reason twice in the terminal — once as ... hook feedback: and once as ... hook blocking error from command: "<cmd>": <reason>. In the 2.1.220 bundle the one blockingError object is rendered by both ppr(blockingError) (the "blocking error from command" line) and via iud(...) (the "feedback" line). De-duplicating that render for an intentional decision:block would remove the second, redundant surface. (Matches earlier reports for PostToolUse.)
Environment
- Claude Code 2.1.220, macOS (arm64)
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗