SessionStart hooks fire twice on /resume, clobbering first invocation's output
Bug
When resuming a session with /resume, Claude Code fires SessionStart hooks twice — once for the resume event, once for the regular SessionStart. This causes the second invocation's output to overwrite the first invocation's user-visible systemMessage.
Reproduction
- Create a SessionStart hook that does meaningful work on first run and caches the result
- On subsequent runs (cache hit), emit a short "ok (cached)" message with suppressOutput: false
- Start a session (hook runs, does real work, shows detailed output)
- Use /resume to resume the session
- Observe: the detailed first-run output is clobbered by the cached "ok" message from the second invocation
Expected behavior
SessionStart hooks should fire once per /resume, not twice. The hook receives a source field distinguishing "startup" from "resume", so firing it twice (presumably once with each source) is redundant — the hook can branch internally on that field.
Impact
- Wasted computation: Hooks that do real work (venv setup, dependency fetching, config detection) run twice unnecessarily
- Suppressed output: The user never sees what the bootstrap actually did, because the second (cached) invocation's output replaces it
- Workaround required: Plugin authors must make cached responses silent (suppressOutput: true) to avoid clobbering, which means users lose visibility into cache-hit status on normal startups too
Workaround
Make the cached path always emit suppressOutput: true so the second invocation doesn't overwrite the first. This works but is a workaround — the root cause is the double invocation.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗