Feature Request: Hook for assistant text output (PreResponse / PostResponse)
Problem
Currently, Claude Code hooks can intercept tool calls (PreToolUse, PostToolUse, TaskCompleted, TeammateIdle, Stop, SessionStart, PostCompact) but there is no hook for assistant text output.
This means behavioral rules like "don't write filler messages" or "don't write speculative answers" cannot be mechanically enforced. They can only be placed in rules/CLAUDE.md/auto memory as text — which the model can and does ignore under pressure.
Use Case
We built a comprehensive cowork system (Agent Teams + 10 hooks + 7 agents + knowledge base) to enforce implementation quality. Everything that involves tool calls is mechanically enforced. But the assistant's TEXT responses (status updates, filler messages, speculative answers) bypass all enforcement.
Examples of behaviors we cannot enforce:
- "Don't write '대기 중입니다' (waiting) as a standalone message"
- "Don't speculate — verify before answering"
- "Don't apologize without taking corrective action"
- "Report teammate objections to the user accurately, don't minimize them"
These are in auto memory feedback rules, CLAUDE.md, and project rules — but the model ignores them when under task pressure.
Proposed Solution
Add PreResponse or PostResponse hook events:
{
"hooks": {
"PreResponse": [
{
"hooks": [
{
"type": "command",
"command": "bash check-response.sh",
"timeout": 5
}
]
}
]
}
}
- PreResponse: Fires before the assistant's text is shown to the user. Exit 2 = block response + feedback.
- PostResponse: Fires after the assistant responds. Exit 2 = send feedback for correction.
Input would include:
{
"response_text": "대기 중입니다.",
"session_id": "...",
"hook_event_name": "PreResponse"
}
This would allow mechanical enforcement of text output patterns, completing the enforcement gap in the current hook system.
Environment
- Claude Code v2.1.80
- Windows 11, Git Bash
- Agent Teams enabled
- 10 hooks configured (PreToolUse, TaskCompleted, TeammateIdle, Stop, SessionStart, PostCompact)
🤖 Generated with Claude Code
10 Comments
+1. A PreResponse hook would complete the hook lifecycle — currently we can intercept actions but not text output.
Current partial workaround using PostToolUse on the last tool call:
The closest we have today is a PostToolUse hook that checks the model's output after tool execution and provides feedback via stderr:
But this only catches text within tool outputs, not the final response text. A true PreResponse hook would enable:
For now, CLAUDE.md instructions like "don't write filler" work ~90% of the time with Opus, but can't be enforced with exit code 2 the way tool hooks can.
+1 — concrete use case that illustrates why prompt instructions aren't enough.
I run Claude Code as a personal assistant hub (Obsidian vault + MCP integrations). One of my standing rules is "every reference must be a clickable link" — file paths should be wrapped in
obsidian://URIs, domains should behttps://links, task IDs should have context. The instruction is in CLAUDE.md, in memory, and has been reinforced through feedback multiple times.It still doesn't work reliably. The model follows it ~70% of the time. The other 30% I get bare
somefile.mdreferences styled as code but not linked, barepro.portland.govwithout a URL wrapper, or "I found this email" with no actionable link. Instruction-following degrades under competing priorities (long context, complex tasks, multiple agents).A
PreResponsehook would let me run a regex scan for un-linked references and inject a correction message before the response renders — rather than after, which just adds noise. TheStophook doesn't solve this because by the time it fires, the user has already read 4 paragraphs of un-linked text.This is fundamentally a formatting enforcement problem that can't be solved with stronger prompting. It needs a hook.
+1 on this. Concrete use case from a developer workflow:
I run a UserPromptSubmit hook that injects absolute current time into every message (e.g. [NOW: 2026-04-20 16:54 Mon]). The agent reads it, acknowledges the correct time, and still emits relative phrasings like "tonight" at 16:54 on a Monday afternoon. Substrate is correct; language at emission overrides.
I've captured this as an agent learning that auto-surfaces on feedback. correction moments, but by definition that fires AFTER the
slip has already reached me, so it only teaches across sessions, not within the one where the slip happened. Same-class slip repeated in the very next session after being captured.
A PreResponse hook would close the gap: scan draft text for relative adverbs against the injected NOW, inject an additionalContext rewrite instruction, never emit the broken phrasing.
Broader pattern: any last-mile rule where substrate knows the correct answer but pre-training overrides at emission banned vocabulary, stale-state claims, name collisions. None of the existing hooks (PreToolUse / UserPromptSubmit / PreCompact / Stop) cover the decision-to-delivery window.
+1 on this, ideally so that it fires on "chunks" of output too, not only before or after the whole output.
+1 on the underlying request. The thread already has strong
articulation of the gap — particularly @havnwright's
"decision-to-delivery window" framing, @yurukusa's analysis of why
the current
PostToolUseworkaround only catches text inside tooloutputs, @epechuzal's concrete demonstration that
Stopfiresafter the user has already read the broken text, and @lightrow's
ask for chunk-level firing. Rather than restating those, this
comment adds two design points that haven't come up yet, plus a
brief engagement with @lightrow's chunking suggestion.
1. Hook output convention — mirror
PreToolUse'spermissionDecisionFollowing the established
PreToolUseshape, adapted for responsegating:
allow(default) — deliver as drafted.denywith reason — block delivery; surface the reason to theuser (or back to the model for retry, depending on configuration).
modifywith replacement text — substitute the drafted textbefore delivery. This handles the formatting-enforcement use
cases (e.g., @epechuzal's linkification, @havnwright's
NOW-timestamp rewriting) without bouncing the response and asking
the model to redraft from scratch.
Reusing the
PreToolUseoutput shape keeps hook contractsconsistent across event types and lets existing integrations carry
over their output-parsing logic.
2. Lifecycle symmetry — every model output channel becomes hookable
Picking up @havnwright's "decision-to-delivery window" diagnosis,
the proposed event would complete the lifecycle:
| Lifecycle stage | Hook |
|---|---|
| User message arriving |
UserPromptSubmit|| Tool call about to fire |
PreToolUse|| Tool call completed |
PostToolUse|| Response drafted, not yet delivered |
PreResponse(missing) || Turn ended |
Stop|Today the prose channel is the only model output without a hook
attached, which is exactly the asymmetry producing the
rule-enforcement gap the OP describes. With
PreResponse, everyoutput channel the model produces — tool calls AND prose —
becomes observable and gateable.
Engaging @lightrow's chunking suggestion
A streaming variant — call it
PreResponseChunk— could fire oneach chunk as it's produced rather than only on the full draft.
Trade-offs:
per-fire latency; enables progressive enforcement for very long
responses.
noise); chunk-level rules are harder to write than whole-response
rules (a banned phrase can span chunk boundaries);
modifysemantics are harder (replacing already-streamed bytes vs
truncating the rest).
Could ship as a follow-on with a separate event name if the basic
PreResponselands first. Doesn't need to block the core design.Compatibility / payload-size note
truncation parameter, a separate
response_text_pathfieldpointing to a temp file, or both.
---
🤖 Generated with Claude Code
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
MessageDisplayhook is probably it?This gap is still open. (I can't reopen the issue from my account — no triage permission on this repo — so treat this as a correction on the record, not a reopen.) I want to correct the one post-close suggestion so the thread doesn't leave a misleading pointer.
Not quite.
MessageDisplaydoes exist now — it was added in v2.1.152 (changelog: "Added aMessageDisplayhook event that lets hooks transform or hide assistant message text as it is displayed") — but it's a display-layer filter, not the response-gating hook this issue asks for. That distinction is exactly the one that matters here.Per the current hooks reference:
MessageDisplay→ Can block? No → "The original text is displayed". There is no pre-delivery gate.displayContentreplaces the displayed text on screen. Display-only: the transcript and what Claude sees keep the original." You can change what is painted on the terminal, but the original text still lands in the transcript and in the model's own context.That breaks every enforcement use case in this thread:
displayContentwould only mask them on screen; the transcript keeps the baresomefile.md, so downstream tooling (and the model) still see the unenforced text.What is still missing is a hook in the decision-to-delivery window (@havnwright's framing) that can
deny/modifythe actual response — not just its rendering. There is noPreResponseorPostResponsehook in the current docs, andStopstill fires too late to rewrite.MessageDisplayis a genuinely useful display filter, but it does not close this gap.(Sources: hooks reference and changelog at code.claude.com, v2.1.152 entry, verified 2026-06-10.)
🤖 Generated with Claude Code
Re-filed as #66749 with the current hook landscape (notably:
MessageDisplayshipped in v2.1.152 but is display-layer only and doesn't close this gap). Continuing the discussion there.+2000, seriously similar issues are being closed as "not planned"? Claude is PAINFULLY verbose and bloated of jargon, we need a way to stop this nonsense