SessionEnd hook: transcript file deleted before hook can read it
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
- Configure a
SessionEndhook that checks iftranscript_pathexists:
``bash``
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty')
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
echo "EXISTS"
else
echo "MISSING"
fi
- Run multiple concurrent sessions (e.g., via Conductor with 8 worktrees)
- Let sessions cycle naturally (Conductor recycles sessions for the same workspace)
- Observe: SessionEnd hooks consistently report
MISSINGfor 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:
- Don't delete the transcript until SessionEnd hooks complete. The hook payload already includes
transcript_path— just ensure the file outlives the hook. - Pass transcript content in the hook payload. Add a
transcriptfield 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
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗