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:
- Transform the text before it's shown to the user, or
- 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.
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:tool_input.content/tool_input.new_string/tool_input.file_pathand returnspermissionDecision: denywith a reason. ✅ Works today.Bucket C examples from my CLAUDE.md:
Stopdoesn'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:
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.