[BUG] PreToolUse hook payload `session_id` / `transcript_path` alternate between current and stale UUIDs within one conversation
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'senforceTdd) 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_idend 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:
- Start a Claude Code conversation; do enough work that a
.jsonlis written - Use
/loginmid-session - Continue normal work — subsequent
PreToolUseinvocations 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_pathpointing 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 (
SessionStartreports original session ID with--fork-session) — same class. - Open: #44450 (
transcript_pathuses 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
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗