[BUG] PreToolUse hook payload `session_id` / `transcript_path` alternate between current and stale UUIDs within one conversation

Resolved 💬 3 comments Opened May 13, 2026 by wazum Closed Jun 11, 2026

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/probity@1.3.2 --agent claude-code --debug /tmp/probity-debug.log

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗