Stop hook receives stale transcript - race condition with file flush
Bug
Stop hooks fire before the transcript JSONL file is fully flushed to disk. The hook reads a stale snapshot of the transcript, missing the final assistant content blocks from the current turn.
This is a re-report of #15813 (closed as stale) and #25121 (same issue for SubagentStop). The bug is still present.
Evidence
Using a diagnostic Stop hook that logs transcript state at hook invocation time:
- Transcript at hook time: 626 lines / 352 assistant lines
- Transcript moments later: 656 lines / 373 assistant lines
- Missing: 30 lines including the final text block from the current turn
The hook's jq pipeline (grep assistant | tail -n 100 | jq -rs '..last') consistently returns the second-to-last text block rather than the final one, because the final block hasn't been written yet.
Impact
Any Stop hook that reads the transcript to inspect the assistant's last output will see stale data. This affects the ralph-loop plugin's completion promise detection — the <promise> tag is in the final text block which hasn't been flushed, so the loop never terminates.
Reproduction
- Install the ralph-loop plugin
- Start a loop with
--completion-promise - Output the promise tag as the last text in a response
- The stop hook reads the transcript before the promise text block is written
- Loop continues indefinitely
Expected behavior
The transcript should be fully flushed (fsync) before Stop hooks are invoked.
Environment
- Claude Code version: latest (as of 2026-03-29)
- OS: macOS 15 (Darwin 25.3.0, arm64)
- Shell: zsh / bash 5.3.9
Related issues
- #15813 — Same bug, closed as stale
- #25121 — Same bug for SubagentStop, quantified 15-44ms race window with 64% failure rate
- #8564 — Related stale transcript path variant
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗