SessionStart hook systemMessage not surfaced in VSCode extension side panel (works in terminal CLI)
Bug Description
A SessionStart hook that outputs a systemMessage (standard local hooks.json, no remote-control) renders the message in the terminal CLI but not in the VSCode native extension side panel. The message never appears in the panel — not inline, not as a dialog, not late.
The hook's additionalContext output is ingested correctly on both surfaces (the model receives it); only the user-visible systemMessage is dropped in the extension panel.
This is distinct from #52775 (which is specifically about remoteControlAtStartup rendering systemMessage as a late dismissable dialog in the terminal) and #33612 (hooks in remote-settings.json not firing for non-terminal clients). Here the hook fires, additionalContext lands, remote-control is off, and it's plain local settings — only the panel rendering of systemMessage is missing.
Steps to Reproduce
- Add a
SessionStarthook to.claude/settings.json(or a plugin'shooks.json) that emits both fields:
``json``
{
"hooks": {
"SessionStart": [{
"matcher": "startup|resume|clear",
"hooks": [{
"type": "command",
"command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"hello-context\"},\"systemMessage\":\"hello-visible-message\"}'"
}]
}]
}
}
- Start a session in the terminal CLI →
hello-visible-messageis shown (asSessionStart:startup says: …). - Start a session in the VSCode native extension (open the Claude Code side panel) →
hello-visible-messageis not shown anywhere in the panel.
In both surfaces the model does receive additionalContext ("hello-context"), confirming the hook itself fires correctly on both.
Expected Behavior
The systemMessage from a SessionStart hook should be surfaced to the user in the VSCode extension side panel — matching the terminal CLI, which echoes it at startup. SessionStart systemMessage is commonly used to show session posture/context (loaded config, active skills, repo state) so the user can see it at a glance; that affordance is silently unavailable in the extension.
Environment
- Claude Code VSCode extension:
anthropic.claude-code@2.1.218 - Platform: Linux (WSL2, VSCode Remote /
.vscode-server) - Hook type:
SessionStart(matcherstartup|resume|clear), localhooks.json - Hook output:
{"hookSpecificOutput": {"additionalContext": "..."}, "systemMessage": "..."} - Remote-control: not active
Impact
Plugins/skills that rely on SessionStart systemMessage for a visible posture greeting or environment summary appear to "do nothing" in the VSCode extension, even though the hook is working and the model-facing context is delivered. Authors have no user-visible SessionStart surface in the panel.
3 Comments
Confirming this on a third client, and adding one finding that closes off the documented workaround.
1. Same bug in the macOS desktop app
Not just the VSCode extension panel. Claude Code 2.1.233, macOS 26.5, arm64, plain local
~/.claude/settings.json, no remote-control.Identical
SessionStarthook, same commit, same machine, run on both surfaces:| output | terminal CLI | desktop app |
| --- | :---: | :---: |
|
systemMessage| renders —SessionStart:startup says: …| not shown ||
hookSpecificOutput.additionalContext| ingested | ingested (model receives it) |Verified across two independent fresh desktop sessions. Matches your report exactly: the hook fires,
additionalContextlands on both, only the user-visiblesystemMessageis dropped on the non-terminal client.2. exit-2 stderr is dropped too — so there is no fallback
This is the part I think is new. The hooks documentation offers exit 2 as the other user-visible path for this event:
I tested it with a throwaway hook that writes an unmistakable marker to stderr and exits 2:
Wired as a second
SessionStarthook alongside the first:| output | terminal CLI | desktop app |
| --- | :---: | :---: |
| exit-2 stderr | renders —
SessionStart:startup hook error … EXIT2-PROBE-9F3A| not shown |The CLI is a clean positive control here: it rendered both channels in the same session, so the probe fired and the wiring was correct. Two desktop sessions showed neither marker.
So on non-terminal clients there is currently no way for a
SessionStarthook to say anything to the user.systemMessageis dropped, and the documented exit-2 fallback is dropped as well. The only thing that survives isadditionalContext, which reaches the model — so the user learns about it only if the model happens to mention it.Why this matters beyond cosmetics
SessionStartis where "state you should know before you start" naturally lives. In my setup this silently disabled four such hooks for a desktop-primary user, including one whose entire job is to announce that the guard kill-switch is active — i.e. a warning that all safety hooks are disabled had 225 recorded firings and was never once visible to the human.A daily backup job in the same setup failed for four days while a
SessionStarthook reported it perfectly — to the model.Repro (desktop app)
~/.claude/settings.json:``
json
``{"hooks":{"SessionStart":[{"hooks":[{"type":"command",
"command":"echo '{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"ctx-marker\"},\"systemMessage\":\"VISIBLE-MARKER\"}'"}]}]}}
VISIBLE-MARKERappears.Happy to test a candidate fix on the desktop app if that helps.
Confirming on a fourth client configuration — the Windows desktop app — and adding a same-session control that narrows the bug to the SessionStart event specifically, rather than notice rendering in general.
Windows desktop app, Claude Code 2.1.251
SessionStarthook emits the standard two-channel envelope on stdout:``
json
``{"systemMessage": "<user-facing text>", "hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "<model-facing text>"}}
additionalContextis ingested (the model demonstrably received and acted on it in the same session), proving the envelope parsed and the hook fired.systemMessagenever renders. Verified by scrolling to the very top of the session transcript: nothing appears above the first user message — not inline, not as a dialog, not late. Matches the VSCode-panel report and the macOS desktop confirmation above exactly.Control: Stop hook
systemMessagerenders fine on the same surface, same sessionIn the same desktop-app session, a
Stophook returning a root-levelsystemMessagerendered normally as a visible "Claude Code notice" in the transcript. So the desktop app's notice rendering works in general — the drop is specific to the SessionStart event (consistent with the SubagentStart/Setup family sharing a startup-time path, if that's the mechanism).Docs note: the hooks reference documents
systemMessagefor both the SessionStart and Stop sections identically as "Displayed in the transcript as a system message," with no per-surface caveat — so on non-terminal clients the SessionStart half of that is currently not true.Practical impact matches the OP: the only reliable user-visible delivery for SessionStart information on these surfaces is asking the model to relay
additionalContext— a workaround, not a channel.Independent repro (5th, per the thread) — same symptom, different platform combo:
.claude/settings.json,SessionStarthook (no matcher), local Node command hook emitting{"systemMessage": "...", "hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "..."}}additionalContextreliably reaches the model's context every session (verified via the model surfacing it back).systemMessagenever appeared anywhere in the UI across multiple fresh session starts — no inline text, no dialog, no notification, immediate or delayed.hasTrustDialogAccepted: true, confirmed), hook not firing (disproved byadditionalContextlanding), malformed JSON (hook output validated manually via both PowerShell and Git Bash before relying on it in-session).Workaround in use: drop
systemMessageentirely and instruct the model viaadditionalContextto proactively relay the status as its own chat text at session start, since that's the only channel that's actually reaching the user on this client today.