Hook event 06 of 33

MessageDisplay

Runs on batches of assistant text as Claude Code renders them and can replace only the text shown on screen.

Exit code veto
Exit code cannot block. Exit 2 cannot block the message; the original text always displays.
Matchers
none
if filter
Does not apply
Docs checked
2026-08-29

Text arrives in batches

You see Claude’s reply appear line by line, but the hook does not wait for the whole message. In an interactive session the reply streams, and MessageDisplay fires for each batch of lines as it becomes ready to render. In a non-interactive run it fires once per completed assistant message. There is no matcher. The hooks reference gives this event a 10-second command timeout, down from the 600-second default.

The payload carries turn_id, message_id, a zero-based index, final, and delta, the text of this batch. index and final tell a hook where the batch sits in the message and whether more are coming.

The screen changes; the transcript does not

This is the hidden layer: what you see and what Claude remembers are two different things. Exit 0 with a valid hookSpecificOutput.displayContent, under hookEventName: "MessageDisplay", replaces what appears on screen for that batch. Without it, the original delta displays. systemMessage and continue are discarded, and there is no decision field. On every exit code the transcript, and the context Claude reasons over, keep the original text.

Failure shows the original

When the hook breaks, you get the unmodified text and nothing else changes. Exit 1 displays the original and records the failure in the debug log. Exit 2 displays the original too. It cannot hold the message back. A timeout does the same. So a broken MessageDisplay hook degrades to no hook.

The mistake most people make first

You write a hook that redacts anything shaped like an API key from the screen, test it, and see the key replaced with asterisks. Done, you think.

The key is still in the transcript file and still in Claude’s context. displayContent changes the rendering of one batch and nothing else. You have hidden it from the one person who was already allowed to see it.

To keep the key out of context, stop it upstream: a PreToolUse hook that denies the Read, which module 09 builds as block_secrets.