Hook to post-process assistant output before TUI render (refile of #56428)

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 12, 2026

Refiling #56428, which was closed by the stale bot on 2026-06-30 with
"Please open a new issue if this is still relevant" rather than by a
maintainer decision. It is still relevant, and I now have a concrete
recurring case.

Request

A new hook event (e.g. AssistantMessage or PreRender) that receives
the assistant's final message text and can return a transformed string
before it renders to the user.

Use case

Deterministic enforcement of output-style rules without burning tokens
on Stop-hook retry loops:

  • Hard-wrap prose at N chars (terminal width preference)
  • Strip filler phrases
  • Redact patterns (secrets, internal hostnames)
  • Inject footers / signatures
  • Convert markdown flavors

Why existing hooks don't cover it

  • Stop fires after the stream is rendered. It can only block and

force a retry — costly, non-deterministic, adds latency.

  • UserPromptSubmit runs pre-model and cannot see output.
  • SessionStart injection is instruction-only, so it is subject to the

same drift as CLAUDE.md.

  • There is no PostToolUse equivalent for the assistant message.

Scope: all assistant text output, not one formatting rule

To be explicit, since the example below is a width case: the request is
a general post-processing point for assistant text, not a line-wrap
feature. Width is simply the instance easiest to demonstrate. The same
hook covers length caps, structural rules (no tables, no headers, no
bulleted walls), house style, redaction, and format conversion — any
rule expressible as a text transform.

The unifying property is that none of these need model judgment. They
are the rules a user can state exactly, which is precisely the set that
should not depend on probabilistic adherence.

Concrete case since the original filing

I keep a stated preference for output hard-wrapped at ~75 chars, held
in both CLAUDE.md and persistent memory, and reinforced by a
SessionStart hook that injects a terseness block every session. In a
long working session today the model still drifted well past it, and I
had to interrupt the work to say I could not read the output.

Two things make this specifically a post-processing problem rather than
a prompting problem:

  1. Wide markdown tables cannot wrap at all. Once emitted they run

off the terminal regardless of any width instruction. A text
post-processor could reflow or convert them; the model can only be
asked to avoid them, which it does unreliably over long contexts.

  1. Drift scales with conversation length. Instruction adherence

decays exactly when a session has accumulated the most context —
which is when readable output matters most.

Neither is a judgment call. Line wrapping and table reflow are
deterministic text transforms, which is what makes a pure-text hook the
right layer.

To put it plainly: this is a tooling gap, not a prompting problem.
Instruction adherence is probabilistic by nature, so no amount of
strengthening the wording converts a formatting preference into a
guarantee. Every other class of rule in Claude Code that must hold
deterministically — permissions, tool gating, session end — has an
enforcement point. Output formatting is the one that does not, and it
is the class least in need of model judgment.

Shape suggestion

Input JSON on hook stdin:

{
  "hook_event_name": "AssistantMessage",
  "message": "...assistant text...",
  "transcript_path": "/path/to/transcript.jsonl",
  "session_id": "..."
}

Hook returns JSON on stdout:

{"replacement": "...new text..."}

Or exits 0 with empty stdout to pass through unchanged.

Precedent

Stop already gates session end. This would be the symmetric mutation
point one layer earlier, between model completion and TUI render.

Prior interest

#56428 drew a +1 from @liviuignat, who wanted the same hook for PII
redaction via Presidio — a case where instruction-only enforcement is
not merely unreliable but unacceptable.

View original on GitHub ↗