Stop hook systemMessage JSON not rendered as visible Line in v2.1.114 (plugin-scope dispatch)

Resolved 💬 6 comments Opened Apr 18, 2026 by jphein Closed May 26, 2026

Summary

In Claude Code 2.1.114, Stop hooks dispatched from a plugin (hooks.json) no longer render their {"systemMessage": "..."} JSON output as a visible <Line> in the terminal, even though the hook script exits 0 and outputs clean JSON to stdout.

Settings.local.json-registered Stop hooks appear to have broken more thoroughly in 2.1.114 (no invocation at all in some sessions), which led me to migrate the hook to a plugin. The plugin-scope dispatch does invoke the hook, it does read its stdout, and the subsequent stop_hook_active:true handling works — but the systemMessage no longer surfaces to the user.

Environment

  • Claude Code: 2.1.114 (Claude Code) (binary symlinked from ~/.local/bin/claude)
  • OS: Ubuntu (Linux 6.17)
  • Plugin: MemPalace fork — registered Stop + PreCompact via plugin hooks.json
  • Hook script: bash ${CLAUDE_PLUGIN_ROOT}/hooks/mempal-stop-hook.sh → invokes python -m mempalace hook run --hook stop --harness claude-code

What I observe

The hook runs. The hook saves data. The hook prints JSON to fd 1 (stdout), separate from any log noise on fd 2 (stderr). Verified by running the exact script Claude Code dispatches:

$ echo '{"session_id":"...","transcript_path":"...","hook_event_name":"Stop","stop_hook_active":true,"cwd":"..."}' \
  | bash ~/.claude/plugins/cache/mempalace/mempalace/3.3.1/hooks/mempal-stop-hook.sh > out 2> err
$ cat out
{
  "continue": true,
  "suppressOutput": false,
  "systemMessage": "✦ 30 memories woven into the palace — stop, hook, readme"
}
$ cat err
Diary entry: diary_memorypalace_20260418_... → memorypalace/diary/checkpoint

But in the terminal UI, nothing shows — not the Stop says: line, not the , not even a collapsed ● Ran N stop hooks summary. Silent success on the UI side even though the producer side is clean.

Expected behavior

Per prior documentation and my own prior working session (2026-04-10 through 2026-04-17, pre-upgrade), a Stop hook outputting {"systemMessage": "✦ ..."} renders as a visible single-line <Line> element via AttachmentMessage.tsx handling hook_system_message attachments. Described as: "silent success is invisible by design — but the systemMessage Line is the one visible signal."

What I tried

  1. Confirmed the hook fires (diagnostic probe file at /tmp/stop-hook-probe shows Claude Code invoking the script).
  2. Confirmed stdout contains clean JSON (not mixed with stderr log lines).
  3. Added continue: true, suppressOutput: false alongside systemMessage — no change in rendering.
  4. Marker advances and diary drawers are written — the save path is healthy.

So the regression appears to be on the UI side: either AttachmentMessage.tsx no longer emits the hook_system_message <Line> for plugin-dispatched Stop hooks, or the attachment is being suppressed somewhere between hook-output capture and render.

Repro (minimal)

Any plugin with a Stop hook that outputs {"systemMessage": "test"} to stdout. Expected: a visible Stop says: test line. Actual: nothing visible, even though save-side side effects succeed.

Impact

Any plugin using silent-save patterns (non-blocking, direct data writes with user-visible confirmation via systemMessage) loses its only visibility signal. The pattern is deliberately non-blocking by design; systemMessage was the UX bridge. Without rendering, users can't tell anything happened.

Additional context

Happy to provide additional traces if helpful.

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗