UserPromptSubmit hook systemMessage does not render in the Desktop app (or VS Code extension)
Summary
A UserPromptSubmit hook that returns only {"systemMessage": "..."} (a non-blocking advisory message, no decision field) is silently dropped — it never reaches the user's view. This is reproducible on both the Claude Desktop app and the VS Code extension. The blocking case ({"decision":"block", "reason":"..."}) is unaffected and renders correctly everywhere via its own dedicated UI treatment.
Environment
- Claude Code CLI version: 2.1.204 (bundled version string found in the installed npm package; exact
claude --versionmay differ by channel) - Reproduced on: Claude Desktop app (
CLAUDE_CODE_ENTRYPOINT=claude-desktop) - Same underlying gap previously confirmed on the VS Code extension (
CLAUDE_CODE_ENTRYPOINT=claude-vscode) — worked around downstream, see below
Repro steps
- Register a
UserPromptSubmithook that, for some prompts, emits only:
``json`
{"systemMessage": "example advisory message"}
decision
with no field and no hookSpecificOutput.additionalContext`.
- Run a Claude Code session from the Desktop app (or the VS Code extension) and submit a prompt that triggers this hook output.
- Observe: the hook fires (confirm via the hook's own logging/exit code — ours logs to a debug file and exits 0), but no message appears anywhere in the session UI, and the assistant's context does not reflect it either.
Expected
systemMessage should render to the user (e.g. as an inline notice) the same way it does in the raw CLI terminal, regardless of which client surface (CLI, Desktop, VS Code, JetBrains) is hosting the session.
Actual
systemMessage is silently dropped on at least Desktop and VS Code. Only hookSpecificOutput.additionalContext reliably reaches the session (via the model's context, since the model can relay it in a reply) — but that's a workaround, not equivalent to a genuine user-facing UI element, and it depends on the model choosing to mention it.
Downstream impact / workaround
We maintain a UserPromptSubmit hook (a model/effort right-sizing advisor) that hit this exact gap. We worked around it hook-side by also populating hookSpecificOutput.additionalContext with the same text whenever CLAUDE_CODE_ENTRYPOINT is anything other than the CLI baseline, so the model relays the message in its reply. That's a reasonable mitigation for us, but it shouldn't be necessary — systemMessage is documented as the mechanism for surfacing a hook notice to the user, with no documented caveat that it's CLI-only.
Additional notes
- The
decision:"block"path is unaffected — it renders via its own dedicated "Prompt blocked by a hook" UI treatment on both Desktop and VS Code, confirmed independently. - We could not find any existing documentation (hooks.md / hooks-guide.md) noting a client-specific difference in
systemMessagerendering, nor any existing issue reporting this. - Happy to share the specific hook payloads/JSON we used to reproduce if useful.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗