Stop hook systemMessage JSON not rendered as visible Line in v2.1.114 (plugin-scope dispatch)
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+PreCompactvia pluginhooks.json - Hook script:
bash ${CLAUDE_PLUGIN_ROOT}/hooks/mempal-stop-hook.sh→ invokespython -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
- Confirmed the hook fires (diagnostic probe file at
/tmp/stop-hook-probeshows Claude Code invoking the script). - Confirmed stdout contains clean JSON (not mixed with stderr log lines).
- Added
continue: true, suppressOutput: falsealongsidesystemMessage— no change in rendering. - 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
- Verified save still works end-to-end (data lands in vector store).
- MemPalace plugin upstream PRs with related workarounds: https://github.com/milla-jovovich/mempalace/pulls?q=is:pr+author:jphein
- Companion fix for a related suppression bug (where
stop_hook_active:truewas silently eating auto-saves in block-mode-guarded silent hooks): filing on the MemPalace side as upstream-fix/silent-save-visibility.
Happy to provide additional traces if helpful.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗