anthropics / anthropics/claude-code

[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

Open
#95,461 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:cowork area:hooks bug platform:web
Dominant language
TypeScript
Stars
147k
Forks
24k
PR merge metrics
PR metrics pending

Description

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's words: "STOP repeating yourself, you fucking idiot!" and, earlier the same day, "Why am I seeing this in every fucking session?"

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

  1. Let a blocking Stop hook suppress the rendered message. A field on the hook's JSON output ("retract": true, or a documented decision value) that removes or collapses the blocked assistant turn in the UI so only the accepted answer is read.
  2. Give the model the blocked text without re-rendering it — deliver the refusal and the draft to the model only, as UserPromptSubmit additional context already does.
  3. Expose to each Stop hook what other Stop hooks have already refused this turn (their names and messages), so a hook can stand down rather than deadlock.
  4. 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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with related issue #83229 and trace the Stop-hook flow for Cowork web and cloud sessions, including how blocked text is sent to the model and rendered. Compare this with the UserPromptSubmit additional-context behavior mentioned here. Done means blocked drafts are not shown to users, or the refused-output behavior and author guidance are documented if suppression is not feasible.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design, cloud
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.