Skip to content

Hooks can't gate assistant chat text — only tool-call and user-prompt boundaries #52844

@SmartLayer

Description

@SmartLayer

Summary

Hooks cover user prompts (UserPromptSubmit), tool calls (PreToolUse/PostToolUse), stops, and compaction — but nothing fires on the assistant's free-form chat text as it's being finalized. Rules whose trigger is the model's own output have nowhere to live except the system prompt / CLAUDE.md, consuming context every turn even when the trigger isn't present.

Concrete context

Pruning ~/.claude/CLAUDE.md, I found rules fall into three buckets by trigger source:

  • A. User-keyword triggered — can move to UserPromptSubmit regex hooks that inject the rule only when the prompt matches. ✅ Works today.
  • B. Claude-about-to-write-a-file triggered — can move to PreToolUse on Write|Edit that scans tool_input.content / tool_input.new_string / tool_input.file_path and returns permissionDecision: deny with a reason. ✅ Works today.
  • C. Claude-chat-text triggered — ❌ no hook fits.

Bucket C examples from my CLAUDE.md:

  • Correcting name misspellings Claude produces in chat (e.g. "Wei Wu" → "Weiwu")
  • Temporal framing: Claude about to describe a past event as "upcoming" because the source document was authored earlier
  • Filler phrases, style drift, provenance slips ("talking to user" vs "writing into project files")

Stop doesn't solve these: it fires after the message is already streamed to the terminal. Exit-2 / decision: \"block\" forces a follow-up turn, but the wrong text stays on screen and the correction is visible as a separate message.

Request

A hook event that fires on each assistant message as it is being finalized, able to either:

  1. Transform the text before it's shown to the user, or
  2. Reject and force regeneration, with structured feedback to the model (analogous to PreToolUse permissionDecision: deny + permissionDecisionReason, but for assistant messages rather than tool calls)

Why this matters

Every bucket-C rule has to sit in the system prompt every turn for a <5% hit rate. On a tuned CLAUDE.md that's a real chunk of context paying the cache-miss cost every session start. A pre-finalize hook would let these rules be lazy-loaded the same way UserPromptSubmit and PreToolUse already allow for buckets A and B.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions