Problem
Claude Code fires the SessionStart hook multiple times within a single session — not just on startup, but also on resume, clear, and compact (distinguished by the event's source field). The plugin's SessionStart handling has no guard against re-fires, so it runs its startup work every time.
In cmd/on-event/main.go:
main.go:552-560 — overwrites the saved trace context (SaveTraceContext) on every SessionStart.
main.go:614-628 — re-runs otlp.CheckConnectivity(cfg) (a network round-trip) and re-prints the user-visible status message on every SessionStart:
if hookEvent == "SessionStart" {
if cfg.OTLPUrl == "" {
printHookResponse("dash0: telemetry is not active — …", "")
} else if err := otlp.CheckConnectivity(cfg); err != nil {
printHookResponse(fmt.Sprintf("dash0: connectivity check failed — %v", err), "")
} else {
printHookResponse(fmt.Sprintf("dash0: connected (v%s)", version.Version), "")
}
}
The hook payload is not inspected for source, so a resume/compact/clear is indistinguishable from a fresh startup.
Symptoms
- The user sees
dash0: connected (vX) (or the not-active / connectivity-failed message) repeatedly within one session — once on startup and again after every compaction, resume, or /clear.
- A redundant connectivity network call fires on each of those events.
- The saved trace context is clobbered mid-session, which can disrupt trace-context continuity for an in-progress turn when a
compact/resume lands between UserPromptSubmit and Stop.
Request
Make SessionStart initialization run once per session:
- Guard the connectivity check + status message (and the trace-context init) so subsequent
SessionStart fires for the same session are no-ops. Options:
- Gate on the event's
source field (source == "startup" only), and/or
- Use a per-session marker in
sessionDir (e.g. a started sentinel file) written on first SessionStart and checked on subsequent ones — robust even if source semantics change.
- On a re-fire, do not overwrite existing trace context (or only initialize it when absent).
Note: the per-session sessionDir already exists (main.go:546), so a sentinel file there is a natural fit and is cleaned up at SessionEnd with the rest of the directory.
🤖 Generated with Claude Code
Problem
Claude Code fires the
SessionStarthook multiple times within a single session — not just onstartup, but also onresume,clear, andcompact(distinguished by the event'ssourcefield). The plugin'sSessionStarthandling has no guard against re-fires, so it runs its startup work every time.In
cmd/on-event/main.go:main.go:552-560— overwrites the saved trace context (SaveTraceContext) on everySessionStart.main.go:614-628— re-runsotlp.CheckConnectivity(cfg)(a network round-trip) and re-prints the user-visible status message on everySessionStart:The hook payload is not inspected for
source, so aresume/compact/clearis indistinguishable from a freshstartup.Symptoms
dash0: connected (vX)(or the not-active / connectivity-failed message) repeatedly within one session — once on startup and again after every compaction, resume, or/clear.compact/resumelands betweenUserPromptSubmitandStop.Request
Make
SessionStartinitialization run once per session:SessionStartfires for the same session are no-ops. Options:sourcefield (source == "startup"only), and/orsessionDir(e.g. astartedsentinel file) written on firstSessionStartand checked on subsequent ones — robust even ifsourcesemantics change.Note: the per-session
sessionDiralready exists (main.go:546), so a sentinel file there is a natural fit and is cleaned up atSessionEndwith the rest of the directory.🤖 Generated with Claude Code