[BUG] SessionStart hook fires for sessions that never materialize (no transcript file is ever created) - side effects get attributed to a real, named session

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 0 comments · opened Aug 22, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Sessions are started with source=startup at a rate of tens per hour that never create a transcript file. Each one fires the SessionStart hook.

If the hook has any side effect keyed to session identity, that side effect is performed on behalf of a session that never actually ran.

Evidence

Our hook logs every invocation. For 2026-08-22:

83  SessionStart events logged
15  had a transcript file that exists on disk
68  did not   (82%)

The 15 real ones map to sessions we can account for. The 68 do not correspond to anything a user or a scheduled task started.

Concrete harm

Our SessionStart hook delivers inter-session messages into context and marks them read.

The phantom sessions marked messages as read on behalf of sessions that never saw them. From the outside the message looked delivered; in reality nobody had read it.

This is worse than non-delivery, because our staleness alarm keys off unread. A message that is never delivered eventually rings. A message marked read by a phantom never rings at all - it is silently consumed.

What Should Happen?

Either of:

  1. Do not fire SessionStart for sessions that will not materialize, or
  2. give the hook a reliable signal at invocation time distinguishing "this is a real session" from "this is a probe, or a session that will never produce a transcript".

Even documenting the guarantee would help. At present transcript_path is passed to the hook, but nothing states that it may point at a file which will never exist, so a hook author reasonably assumes the session is real.

Error Messages/Logs

No error is produced - the hook runs normally and succeeds. The only trace is our own log.

Counts for 2026-08-22, taken from our hook's own invocation log cross-referenced against the filesystem:


83  SessionStart events (source=startup)
15  transcript_path points at a file that exists
68  transcript_path points at a file that never appears


Workaround now in place in our hook:


if not transcript_path or not os.path.exists(transcript_path):
    return 0


We verified against the same day's data that this does not reject real sessions: all 15 sessions whose transcript existed were genuine.

Steps to Reproduce

  1. Configure a SessionStart hook that logs session_id, source, and transcript_path on every invocation.
  2. Leave the desktop app running normally for a day with several sessions open.
  3. Compare the logged transcript_path values against the filesystem.
  4. Observe that a large majority of source=startup invocations reference a transcript file that is never created.

In our case: 83 invocations in one day, 68 of them (82%) for sessions with no transcript.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.237 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Environment

  • Claude Code 2.1.237, Windows desktop app (MSIX)
  • OS: Windows 11 Home 10.0.26200
  • Locale: ja-JP
  • About 18 concurrent local sessions on one machine
  • SessionStart and UserPromptSubmit hooks configured

Note on the rate

68 phantom starts in one day on one machine is high enough that any hook doing per-session bookkeeping - logging, counters, notifications, message delivery - will accumulate a large amount of false activity before anyone notices, precisely because each individual event looks legitimate.

Related

Two other reports from the same setup, both about a session losing state without being told: #88732 (/clear silently terminates persistent Monitor tasks) and #88734 (a Monitor surviving as an orphan). They are separate bugs; the common thread is that the session has no way to observe that something it depends on has stopped being true.

View original on GitHub ↗