[BUG] SessionStart hook systemMessage silently dropped on --continue/--resume/fork when additionalContext matches one already in the transcript
Preflight
Searched existing issues. Related but different: #93458 (desktop app rewind, source=fork, a changed additionalContext never reaches the model). In the terminal CLI on 2.1.281 I see a narrower, deterministic mechanism: a changed additionalContext is injected fine on resume and fork; the drop happens only when the new additionalContext is identical to one already in the transcript, and it takes the user-facing systemMessage down with it.
What's Wrong?
On --continue, --resume <id> and --resume <id> --fork-session, a SessionStart hook's systemMessage is silently not displayed whenever the hook's additionalContext is byte-identical to a SessionStart additionalContext already present in the transcript.
- Nothing is shown to the user (no
SessionStart:resume says: ...line). - Neither
hook_system_messagenorhook_additional_contextis written to the transcript for that launch. - The debug log still says
Hook SessionStart (...) provided additionalContext (N chars), with no trace of the drop.
Skipping a duplicate additionalContext is reasonable (the model can still see the earlier identical copy in the history; I verified this with a marker that only ever appeared inside the attachment, and the model recalled it on the next resume). The problem is that the user-facing message of the same batch is discarded too.
The same drop happens when:
- the
additionalContextis unchanged but thesystemMessagetext is new (0/2 displayed); - the hook emits only a
systemMessage(noadditionalContext) and the transcript already holds any SessionStartadditionalContext(0/2 displayed).
This is not a timing issue: with a random additionalContext on every launch, all 27 resume / continue / fork / compact launches displayed correctly, including hooks sleeping 0, 1, 3 and 8 seconds.
What Should Happen?
The hook's systemMessage is displayed on every launch. Deduplicating model-facing context against the transcript is fine, but display-only output should not be dropped with it.
Steps to Reproduce
- In an empty directory, create
.claude/settings.jsonwith a SessionStart hook that prints:
``json``
{"systemMessage": "notice <random each run>", "hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "context <random each run>"}}
- Run
claude, send one message,/exit. Runclaude --continuea few times: theSessionStart:resume says: notice ...line is shown every time. - Change the hook so
additionalContextis a fixed string (keep the randomsystemMessage). claude --continue: the line is shown once (the fixed text is new to this transcript).claude --continueagain, as often as you like: noSessionStart:resume says:line appears, and the transcript has no SessionStart attachment for those launches. In my runs this was 6/6 dropped across--continue,--resume <id>,--fork-session, after/compact(which resets the baseline once), and withtui: "fullscreen".- Switch
additionalContextback to random: every launch displays the line again.
Workaround: register a second SessionStart hook that emits a short, unique additionalContext (for example a timestamp). The batch then always has a new key and the first hook's systemMessage is kept (2/2), while its identical additionalContext is still deduplicated.
Likely cause
On resume, the SessionStart messages are deduplicated against the existing transcript, keyed on additionalContext and non-empty hook_success content. When no keyed item in the batch is new, the helper returns an empty list, which also discards the unkeyed hook_system_message entries of the same batch.
Suggested fix: deduplicate only the model-facing context and always keep display-only attachments (hook_system_message).
Claude Code Version
2.1.281 (native install)
Platform
Anthropic API (Claude subscription)
Operating System
macOS (Darwin 25.1.0)
Terminal/Shell
Terminal CLI inside tmux, zsh. Reproduced with the hook registered both through a plugin and through settings.json.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗