[BUG] A Stop hook's blocked reply is still shown to the user, so every retry reads as the assistant repeating itself
This concerns Cowork on claude.ai in the browser and the Claude Agent SDK's Stop hook, not the CLI, so the CLI-only required fields are not applicable.
What happens
When a Stop hook returns {"decision": "block", "reason": "..."}, the assistant message that was blocked is still rendered to the user. The model then rewrites the reply to satisfy the hook. The user sees both, and since the rewrite says substantially the same thing, the conversation reads as the assistant compulsively repeating itself.
With several Stop hooks installed, each bounded independently, one turn can produce four, six or eight near-identical paragraphs in the transcript. Observed repeatedly across sessions in this account; the user's words were "you keep repeating yourself — stop it".
Two secondary effects that make it worse:
- The model has no signal that the blocked text was displayed. From its side the reply "did not go out", so the natural repair is to re-send the whole reply rather than only the delta. Nothing in the hook result says the prior attempt was already read.
- The block reason is re-delivered as a user turn. A hook whose reason text contains phrases it also matches on (ours matched "safe to close") re-triggers itself on the retry, looping until its own internal counter stops it.
What would fix it
Any one of these:
- Do not render an assistant message that a Stop hook blocked, or mark it in the UI as retracted.
- Include in the next turn's context an explicit note that the blocked text was shown to the user, so the model sends only what changed.
- Tag the re-delivered hook reason so it is distinguishable from user input by the hooks that read the transcript.
Environment
- Cowork on claude.ai, Chrome on macOS; Opus 5
- Multiple Stop hooks from an installed plugin
- Related: #95408 (same class, turn separators collapsing around auto-compaction)