[BUG] A blocking Stop hook's refused reply is still rendered to the user, so every refusal shows them the whole answer again — and two hooks can deadlock with no way out
Cowork on the web, cloud sessions, Stop hooks that exit 2.
What happens
When a Stop hook blocks, the assistant's message has already been streamed to the user. The hook feedback goes to the model, the model rewrites, and the rewrite is rendered too. The user therefore reads every refused attempt. There is no retraction, no "this answer was withheld" state, and nothing the hook can do about it — by the time the hook runs, the text is on screen.
Measured today on one closing message: four renders of near-identical text for one answer. The user objected strongly to the repetition, more than once that day.
The part that makes it unrecoverable
Independent Stop hooks cannot see each other, so two can impose contradictory requirements and the model cannot satisfy both:
- Hook A refuses a closing message that omits four specific mandatory sentences.
- Hook B refuses any paragraph the user has already been shown in this session.
Once A has forced those sentences once, B refuses them forever, and A refuses their absence. Every iteration renders in full. The only escape we found was wrapping the mandatory text in a code fence, because hook B happens to skip fenced blocks — an accident of that hook's implementation, not a mechanism the platform offers.
Asks, any one of which fixes it
- Let a blocking
Stophook suppress the rendered message. A field on the hook's JSON output ("retract": true, or a documenteddecisionvalue) that removes or collapses the blocked assistant turn in the UI so only the accepted answer is read. - Give the model the blocked text without re-rendering it — deliver the refusal and the draft to the model only, as
UserPromptSubmitadditional context already does. - Expose to each
Stophook what otherStophooks have already refused this turn (their names and messages), so a hook can stand down rather than deadlock. - Failing all three, document that refused output is user-visible, so hook authors write refusals that ask for a delta rather than a rewrite.
(1) is the one that matters; the rest are mitigations.
Related
#83229 covers a blocking Stop hook reprinting the whole answer; this adds the multi-hook deadlock, which has no workaround at all, and a measured four-render case.