SessionEnd hook: transcript file deleted before hook can read it

Resolved 💬 1 comment Opened Mar 2, 2026 by cheapsteak Closed Mar 7, 2026

Bug Description

When a session ends (especially in multi-session environments like Conductor), the SessionEnd hook receives a valid transcript_path in its input, but the file has already been deleted by the time the hook tries to read it.

This is related to #15813 (Stop hook sees stale transcript data) but is a more severe variant: the transcript file is entirely missing, not just stale.

Reproduction

  1. Configure a SessionEnd hook that checks if transcript_path exists:

``bash
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty')
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
echo "EXISTS"
else
echo "MISSING"
fi
``

  1. Run multiple concurrent sessions (e.g., via Conductor with 8 worktrees)
  2. Let sessions cycle naturally (Conductor recycles sessions for the same workspace)
  3. Observe: SessionEnd hooks consistently report MISSING for the transcript file

Evidence

Diagnostic logging from real sessions:

2026-03-02T16:15:12Z sid=81d06cad event=SessionEnd transcript=MISSING path=/.../chiang-mai/81d06cad-....jsonl
2026-03-02T16:15:12Z sid=4aa99d8c event=SessionEnd transcript=MISSING path=/.../amarillo-v1/4aa99d8c-....jsonl

Same worktrees show two different session IDs within minutes — the earlier sessions captured data via Stop hooks (file existed), but by SessionEnd the file was gone:

| Worktree | Session WITH data (Stop) | Session WITHOUT data (SessionEnd) |
|----------|--------------------------|-----------------------------------|
| doha | c43fa5aa at 21:51 | d39f1025 at 21:54 |
| kingston-v1 | 49558920 at 21:51 | 57422139 at 21:54 |

Expected Behavior

The transcript file at transcript_path should exist and be readable for the duration of the SessionEnd hook execution. SessionEnd is the last chance to read transcript data — if it's gone here, it's gone forever.

Suggested Fix

Either:

  1. Don't delete the transcript until SessionEnd hooks complete. The hook payload already includes transcript_path — just ensure the file outlives the hook.
  2. Pass transcript content in the hook payload. Add a transcript field to the JSON stdin, so hooks don't need filesystem access. This eliminates the race entirely.

Option 1 is simpler. Option 2 is more robust (also fixes #15813's staleness issue).

Impact

Any hook that reads the transcript on SessionEnd gets no data. This makes it impossible to reliably extract:

  • What the user was working on (prompts)
  • What files were edited (tool calls)
  • Session summaries

Workaround: capture data on Stop events (while the file still exists) and carry forward via a persistent store. But this means the final session snapshot is incomplete — any work done between the last Stop and SessionEnd is lost.

Environment

  • Claude Code: 2.1.63
  • macOS (Darwin 25.1.0)
  • Multi-session via Conductor (but the bug is in Claude Code's transcript lifecycle, not Conductor)

Related: #15813, #20612

View original on GitHub ↗

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