Skip to content

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

@jphein

Description

@jphein

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions