fix(claude-code): capture Stop transcripts as validated snapshots - #2099
Open
MuskanPaliwal wants to merge 9 commits into
Open
fix(claude-code): capture Stop transcripts as validated snapshots#2099MuskanPaliwal wants to merge 9 commits into
MuskanPaliwal wants to merge 9 commits into
Conversation
MuskanPaliwal
marked this pull request as ready for review
August 21, 2026 17:58
Contributor
Author
|
Hi @Soph, this pr is ready for an early review. Could you take a look and let me know if the overall approach makes sense, especially how I'm handling Claude transcript readiness? |
…pt-readiness # Conflicts: # cmd/entire/cli/lifecycle.go
…pt-readiness # Conflicts: # cmd/entire/cli/agent/agent.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #2091, item 3 only.
Before this change, Claude Code's Stop hook waited for a transcript sentinel that no longer appears, then fell back to a 500 ms quiet window. The wait only validated a mutable path. Lifecycle copied that path later, and provisional-checkpoint finalization opened it once more. Entire could therefore store bytes other than the ones it had treated as ready.
This PR replaces that sequence with one owned, validated snapshot for Claude Stop. Modern Claude payloads provide the final assistant response, so Entire can validate a transcript immediately with no artificial wait. Older payloads retain the 500 ms quiet-window fallback. The lifecycle also keeps checkpoints refreshable across Claude's repeatable Stop events and derives file and token metadata from one transcript parse.
The wait did not own the transcript
TranscriptPreparer returns only an error. It gives the caller no bytes, open handle, identity, size, or file version. A successful wait says that path state A looked ready, but lifecycle can then copy state B and checkpoint finalization can read state C.
Shortening the wait alone would reduce latency while leaving that race intact. Valid final JSON would not be enough either: a complete older prefix can be syntactically valid even when the current turn has not reached the file.
Owned, validated Stop snapshots
The built-in Claude Code adapter now has a Stop-specific capture operation. It:
None of those consumers reopens Claude's live transcript for the same Stop. If the file changes during capture, the bytes are discarded and capture retries against the new version.
The portable fingerprint catches ordinary appends, truncation, replacement, and rewrites whose modification time changes. It cannot prove that a same-size rewrite did not happen when the filesystem also reports the same timestamp. Claude appends transcript records, so that is an acceptable limit here; the tests do not claim stronger detection.
Modern readiness is evidence-based and immediate
For modern payloads, non-empty last_assistant_message supplies current-turn evidence. Capture requires a transcript position successfully measured at TurnStart, reconstructs the latest assistant text only after that boundary, and requires it to match Claude's supplied final response.
The first readiness check now runs immediately. The previous design waited for the first 50 ms ticker event even when the transcript was already complete. After the immediate attempt, incomplete captures return to the normal 50 ms polling cadence.
A rejected file fingerprint is remembered. Entire does not reread and revalidate the same unchanged 70 MiB snapshot every 50 ms; it waits until file identity, size, or modification time provides new evidence.
Missing, stale, incomplete, continuously changing, mismatched, timed-out, and canceled captures return no snapshot. The maximum wait remains three seconds.
Legacy payloads keep the quiet-window fallback
When last_assistant_message is missing, null, or empty, Entire has no producer evidence that the current final response reached the transcript. It therefore retains the 500 ms quiet window used by older Claude Code versions.
The split is intentional:
Repeatable Claude Stops remain refreshable
A blocking Claude Stop hook can cause the same assistant turn to continue and later emit another Stop without a new UserPromptSubmit event. A valid last_assistant_message proves readiness for one Stop; it does not prove that Claude will never continue afterward.
Claude therefore advertises a repeatable turn-end capability. After each Stop, Entire:
The next user prompt or SessionEnd seals the turn. stop_hook_active is preserved as protocol metadata but is not treated as proof that a Stop is permanently final.
If the latest checkpoint refresh fails, Entire keeps the last successfully stored transcript and preserves the checkpoint IDs as recovery bookkeeping. Successful session-end refreshes release those IDs; failed ones keep the supporting shadow-branch state protected from cleanup.
File and token analysis share one parse
Claude file extraction and token calculation previously implemented separate capabilities, each of which parsed the same main transcript and subagent transcripts independently.
The optional TranscriptTurnAnalyzer capability now parses the current main turn once and derives both modified files and token usage from those records. Each subagent transcript is likewise parsed once for both results. Other agents retain the existing independent capability fallback.
Capture validation remains a separate, lightweight safety pass. Its question is whether the bytes are safe and complete; turn analysis answers what metadata those validated bytes contain.
Capture and analysis timing
These are medians from three runs on an Apple M3. They measure transcript capture, validation, and transcript-backed metadata analysis, not complete end-to-end Stop-hook latency.
Modern Claude Stop hook
The realistic large-history case is the third row: the transcript file is 70 MiB, but most records belong to earlier turns. The 433.860 ms case deliberately makes all 70 MiB one enormous current turn.
Legacy Claude Stop hook
Other timing cases:
The common modern case moves from the previous PR design's unavoidable 50 ms first poll to actual processing time: under 1 ms for a small transcript and about 32 ms for a realistic 70 MiB historical session.
Test coverage
Focused tests cover:
The benchmarks live in cmd/entire/cli/agent/claudecode/transcript_capture_test.go.
Verification
Fresh verification for commit 40fbeb0:
The separately reported test totals were:
Kept out of this PR
This PR does not introduce a repository-wide TranscriptPreparer replacement, change external-agent readiness contracts, enforce a minimum Claude Code version, or claim end-to-end Stop-hook latency from the transcript-only benchmarks.