Interactive TUI drops `MessageDisplay` `displayContent` (honored in `--print`) — v2.1.227

Status Closed — duplicate
Reported on v2.1.227
Maintainer reply None cached
Activity 1 comment · opened Aug 11, 2026 · closed Aug 15, 2026

Summary

A plugin MessageDisplay hook that returns hookSpecificOutput.displayContent has its output honored in --print mode but silently dropped by the interactive TUI. The hook fires, emits well-formed JSON, and exits 0; nothing is drawn on screen and no error is logged (/plugin Errors tab is empty). Reproduced in a plain terminal, so it is not terminal-emulator specific.

Environment

  • Claude Code 2.1.227 (MessageDisplay + displayContent shipped in 2.1.152 per CHANGELOG).
  • macOS 15 (Darwin 25.5.0), zsh.
  • Reproduced in every interactive renderer tried — fullscreen inside a Ghostty-based terminal app, fullscreen in a plain Terminal.app, and screen-reader plain-text mode (CLAUDE_AX_SCREEN_READER=1) in a plain Terminal.app. Same result each time, so it is neither host-terminal nor fullscreen-renderer specific: the whole interactive display path drops the emit. Only non-interactive --print honors it.
  • Plugin used: claudish-to-english@gvzdv-plugins v0.1.1 (a MessageDisplay hook that appends a plain-English restatement under long assistant messages). Repo: https://github.com/gvzdv/claudish-to-english

Expected

Per docs (code.claude.com/docs/en/hooks): displayContent "replaces the displayed text on screen. Display-only: the transcript and what Claude sees keep the original." So the emitted content should appear on screen in the interactive TUI.

Actual

The interactive TUI shows the original assistant message unchanged; the displayContent substitution is never painted. No error surfaced anywhere.

Minimal repro

A MessageDisplay hook wired in a plugin's hooks/hooks.json:

{ "hooks": { "MessageDisplay": [ { "hooks": [
  { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/rewrite.sh", "timeout": 60 } ] } ] } }

that, on the final chunk of an assistant message, emits to stdout (exit 0):

{"hookSpecificOutput":{"hookEventName":"MessageDisplay",
  "displayContent":"<final delta>\n\n--------\nIn plain English:\n\nSTUB-SIMPLIFIED ..."}}

Steps:

  1. Enable the hook; start an interactive session.
  2. Send a prompt that yields a reply over ~200 prose chars.
  3. Observe: the appended block does not appear on screen.

Evidence this is a render-path bug, not the hook

  • Hook fires and emits, zero latency (deterministic stub, no network): the debug log shows, for single-chunk and multi-chunk messages alike, stub rewrite on the final chunk with a well-formed emit. Examples from a plain Terminal.app:
  • Fullscreen: 10:02:05 final: prose_len=3782 mode=append full_bytes=4410 then stub rewrite — on-screen, no block.
  • Screen-reader mode (CLAUDE_AX_SCREEN_READER=1): 10:09:35 final: prose_len=3911 then stub rewrite — on-screen, no block.
  • Core honors displayContent in --print: running the same session via claude -p "<prompt forcing >200 prose chars>" with the stub hook, the emitted STUB-SIMPLIFIED marker appears in stdout. So the MessageDisplay plumbing works end-to-end in the print path; only the interactive render drops it.
  • No error anywhere: /plugin Errors tab empty; exit 0; JSON validates.
  • Not latency/timeout: the stub is ~instant and well within the hook timeout; a single-chunk message (substitution emitted at that chunk's display time) also fails.

Ask

Should the interactive TUI apply MessageDisplay displayContent the way --print does? If plugin-sourced MessageDisplay hooks are intended to work interactively, this looks like a render-path gap in 2.1.227. If there is an intended constraint (e.g. only the initial render is substitutable, not a late final-chunk emit), documenting it — and surfacing a warning instead of a silent no-op — would help.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗