claude-code - 💡(How to fix) Fix [BUG] PreToolUse hook payload `session_id` / `transcript_path` alternate between current and stale UUIDs within one conversation [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#58682Fetched 2026-05-14 03:42:10
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×3commented ×2cross-referenced ×1

Root Cause

  • TDD enforcement (@nizos/probity's enforceTdd) reads the transcript at the path it was told. When that path points to a stale file, the validator legitimately concludes "no failing test observed" because the file it was told to read hasn't been updated. The failing test exists — just in a different file than the payload pointed at. Result: false-positive TDD-violation blocks on legitimate work.
  • Audit logging and session analytics lose continuity when half the events land under one session_id and half under another.
  • Hooks that key state files on session_id end up writing to two parallel state files for one conversation.

Fix Action

Fix / Workaround

There is no safe workaround at the hook level: the hook can't tell from a single payload whether the session_id it received is "the current one" or "the stale one", and a directory-scan / mtime-heuristic introduces multi-session race conditions in shared CI.

The alternation suggests either a race between session-rotation logic and hook dispatch, or hook dispatch reading from different cached session-id sources on different code paths.

Code Example

2026-05-13T12:27:03.748Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
2026-05-13T12:27:09.822Z  session_id=dfca14e6-…  transcript_path=/dfca14e6-.jsonl  ← stale
2026-05-13T12:27:11.103Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
2026-05-13T12:27:25.706Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
2026-05-13T12:27:32.232Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
2026-05-13T12:27:55.357Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
2026-05-13T12:28:08.532Z  session_id=dfca14e6-…  transcript_path=/dfca14e6-.jsonl  ← stale
2026-05-13T12:28:30.668Z  session_id=dfca14e6-…  transcript_path=/dfca14e6-.jsonl  ← stale
2026-05-13T12:28:35.744Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
2026-05-13T12:28:59.141Z  session_id=de680159-…  transcript_path=/de680159-.jsonl  ← live
RAW_BUFFERClick to expand / collapse

Bug

Within a single conversation, the PreToolUse hook payload's session_id and transcript_path fields alternate between the currently active session UUID and a stale UUID (frozen from earlier in the same conversation). Different PreToolUse invocations seconds apart receive different values.

Evidence

Captured via the @nizos/probity hook (configured for PreToolUse matching Bash|Write|Edit). All entries below come from a single Claude Code conversation, single Claude Code process, within the same minute:

2026-05-13T12:27:03.748Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
2026-05-13T12:27:09.822Z  session_id=dfca14e6-…  transcript_path=…/dfca14e6-….jsonl  ← stale
2026-05-13T12:27:11.103Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
2026-05-13T12:27:25.706Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
2026-05-13T12:27:32.232Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
2026-05-13T12:27:55.357Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
2026-05-13T12:28:08.532Z  session_id=dfca14e6-…  transcript_path=…/dfca14e6-….jsonl  ← stale
2026-05-13T12:28:30.668Z  session_id=dfca14e6-…  transcript_path=…/dfca14e6-….jsonl  ← stale
2026-05-13T12:28:35.744Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
2026-05-13T12:28:59.141Z  session_id=de680159-…  transcript_path=…/de680159-….jsonl  ← live
  • The "stale" file's mtime stops at ~13:51 local time — write activity to that path ceased earlier in the same conversation.
  • The "live" file is being actively appended to throughout. Mtime is current.
  • Both files exist in ~/.claude/projects/<project-dir>/.

The trigger that started the divergence appears to be a /login slash-command mid-session (the live file was created right after). However, after that, both UUIDs continue to appear in subsequent hook payloads at random, not just the stale one.

Expected behavior

Every PreToolUse invocation in the same conversation should report the same, currently active session_id and transcript_path — the file Claude Code is actually writing to right now.

Impact

Hook-based tools that read the transcript to make decisions cannot do so reliably:

  • TDD enforcement (@nizos/probity's enforceTdd) reads the transcript at the path it was told. When that path points to a stale file, the validator legitimately concludes "no failing test observed" because the file it was told to read hasn't been updated. The failing test exists — just in a different file than the payload pointed at. Result: false-positive TDD-violation blocks on legitimate work.
  • Audit logging and session analytics lose continuity when half the events land under one session_id and half under another.
  • Hooks that key state files on session_id end up writing to two parallel state files for one conversation.

There is no safe workaround at the hook level: the hook can't tell from a single payload whether the session_id it received is "the current one" or "the stale one", and a directory-scan / mtime-heuristic introduces multi-session race conditions in shared CI.

Reproduction (best-effort)

I haven't isolated this to a fully reliable minimal repro. The trigger in the captured session appears to be:

  1. Start a Claude Code conversation; do enough work that a .jsonl is written
  2. Use /login mid-session
  3. Continue normal work — subsequent PreToolUse invocations begin alternating between the original session UUID and a new one

The alternation suggests either a race between session-rotation logic and hook dispatch, or hook dispatch reading from different cached session-id sources on different code paths.

Related

  • Claude Code 2.1.72 changelog: "Fixed several hooks issues: transcript_path pointing to the wrong directory for resumed/forked sessions" — same class of bug; possibly an incomplete fix or a regression at a later version.
  • Open: #39355 (SessionStart reports original session ID with --fork-session) — same class.
  • Open: #44450 (transcript_path uses cwd hash in git worktree) — same class.

Environment

  • Claude Code: 2.1.140
  • Platform: macOS Darwin 25.4.0
  • Hook executor: npx @nizos/[email protected] --agent claude-code --debug /tmp/probity-debug.log

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Every PreToolUse invocation in the same conversation should report the same, currently active session_id and transcript_path — the file Claude Code is actually writing to right now.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING