VS Code extension: Stop-hook-blocked draft and its rewrite both render as duplicate full responses
Environment
- Claude Code 2.1.210, VS Code native extension
- macOS (Darwin 25.5.0)
What happens
When a Stop hook returns {"decision": "block", "reason": ...}, the rejected draft has already streamed into the conversation view. Claude then generates the corrected response, and the VS Code extension renders both: two near-identical, full-length message bubbles back to back. The only differences between them are the spots the rewrite fixed, e.g.:
| First bubble (blocked draft) | Second bubble (re-send) |
|---|---|
| get_editor_state — if it connects | get_editor_state. If it connects |
| ⌘⇧P → "Reload Window" | ⌘⇧P, then "Reload Window" |
To a user this reads as the assistant stuttering a duplicate wall of text, every single time the gate fires.
Repro
- Register a
Stophook that blocks on some detectable property oflast_assistant_message(mine blocks on em dashes / prose length):
``json``
{"hooks": {"Stop": [{"hooks": [{"type": "command", "command": "/usr/bin/python3 ~/.claude/hooks/enforce-response-contract.py"}]}]}}
- In the VS Code extension, elicit a response that violates the rule.
- Observe: blocked draft renders in full, followed by the rewritten response in full.
Expected
The transcript legitimately contains both messages, and I understand the draft cannot be un-streamed. This is a request for UI treatment: the extension knows the first message was rejected by a Stop hook, so it could
- collapse the superseded draft behind a "draft rejected by Stop hook (click to expand)" affordance, or
- visually mark it (dimmed / struck header) as superseded by the message that follows.
Prior art / not duplicates
- #39499 asked to suppress the intermediate output at the hook level; closed as not planned. This issue asks for rendering treatment instead, not suppression.
- #31301 was about a PostToolUse block reason rendering twice; this is about the full draft + rewrite pair.
- #40029 / #49851 are Stop hooks not firing in the VS Code extension at all; here the hook works correctly.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗