SessionStart hooks fire for phantom sessions that never materialize (no transcript is ever created), racing the real session within a few hundred ms

Status Open
Reported on v2.1.211
Maintainer reply None cached
Activity 1 comment · opened Jul 17, 2026

Summary

Claude Code sometimes fires multiple SessionStart hooks for the same project within a very short window (measured 315ms / 488ms apart). One of them belongs to the session the user actually gets; the other belongs to a "phantom" session that:

  • never creates its transcript file (~/.claude/projects/<proj>/<session_id>.jsonl never appears),
  • never fires UserPromptSubmit, Stop, or any subsequent hook,
  • but receives a fully-formed SessionStart payload — including a transcript_path — indistinguishable from the real one at hook time.

Any hook that performs a one-shot, consume-once action keyed on "the next session start" (state handover, token/baton consumption, migrations, telemetry sessions) can be consumed by the phantom, silently breaking the real session.

Measured data (single machine, macOS, VS Code + CLI mix)

From a private hook's decision log covering 3,222 SessionStart events across ~2 months:

  • 56 pairs of SessionStart events fired for the same project within 2 seconds of each other (different session_ids), i.e. near-simultaneous double starts are routine.
  • Phantom characteristics verified against the payload's own transcript_path: the file is never created, while the twin session's transcript exists and keeps growing.
  • Observed on versions from at least 2.1.1xx (2026-05) through 2.1.211 (2026-07-17). Both source: "startup" and source: "clear" twins were observed.
  • Timing note for hook authors: even for the real session, the transcript file is created ~460ms after the SessionStart hook fires, so "does the transcript exist" cannot be used inside the hook to tell real from phantom.

Concrete incident (2026-07-17, times UTC), two SessionStart events 488ms apart for the same cwd:

07:39:59.395  session c4f05b96-…  source=startup  ← phantom: transcript never created, no further hooks
07:39:59.883  session 11e62b74-…  source=startup  ← real: transcript created 07:40:00.344, session continues

Our hook consumed a one-shot handover token at SessionStart; the phantom consumed it first and the real session started without its state. (We have since redesigned the hook to defer consumption to the first UserPromptSubmit, which a phantom never fires — that works as a mitigation.)

Expected behavior

Either of:

  1. SessionStart fires only for sessions that will actually materialize (or a cancellation signal like SessionEnd is delivered for phantoms), or
  2. Documentation explicitly states that SessionStart may fire for sessions that never materialize, so hook authors know not to perform consume-once side effects there.

Repro sketch

We do not have a deterministic trigger; frequency data suggests it correlates with window/webview reloads and /clear in the VS Code extension (double source:"clear" twins were observed in May). Logging session_id + transcript_path existence from a SessionStart hook and diffing against transcripts that actually appear reproduces the detection.

Environment

  • Claude Code 2.1.195 – 2.1.211 (phantoms observed since at least 2026-05 on earlier versions)
  • macOS 15.x (Darwin 25.5.0), VS Code extension + CLI

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗