fix(capture): correlate Stop turn meta with the flushed transcript record#325
Conversation
…cord The Stop hook races the transcript writer: the turn's final assistant record is often not flushed when capture reads the file, so the enrichment either came back null (observed live: every first turn captured with null model/token_usage) or - worse - silently attributed the PREVIOUS turn's tokens to the current one, because the scanner accepted any usage-bearing assistant record regardless of which turn it belonged to. parseClaudeTurnMetaLive re-opens and re-scans the transcript across a bounded backoff (25/50/100ms) and correlates the newest text-bearing, non-sidechain assistant record with last_assistant_message (exact match, or an anchored >=32-char truncation prefix). No fallback to older records: if the current turn's record never materializes, the row is written unenriched and the reason logged. An empty last_assistant_message skips enrichment instead of bypassing the correlation. The trace e2e harness now replays each transcript's REAL final assistant text and selects transcripts with the same live semantics, instead of a synthetic message that would never correlate.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughClaude capture metadata now correlates hook messages with the newest relevant assistant transcript text. Live scanning retries during transcript writes, excludes or includes sidechains by context, and safely skips enrichment when no match appears. E2E selection and tests use the updated flow. ChangesClaude live correlation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CaptureHook
participant LiveParser
participant TranscriptScanner
participant ClaudeTranscript
CaptureHook->>LiveParser: provide last_assistant_message and transcript path
LiveParser->>TranscriptScanner: scan expected assistant text
TranscriptScanner->>ClaudeTranscript: read transcript snapshot
ClaudeTranscript-->>TranscriptScanner: matching metadata or retry
TranscriptScanner-->>LiveParser: TraceModelMeta or no match
LiveParser-->>CaptureHook: enrich event or continue without metadata
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage ReportScope: files changed in this PR. Enforced threshold: 90% per metric (per file via
File Coverage — 2 files changed
Generated for commit 39e697d. |
Problem
The Stop hook races the transcript writer: the turn's final assistant record is often not on disk when capture reads it. Observed in real interactive sessions (validated on this machine):
model=null, token_usage=nulleven though the record landed ~ms later.The #320 e2e could not catch this: it replays settled transcripts, never a mid-flush one.
Fix
parseClaudeTurnMetaLive(src/notifications/model-usage.ts):last_assistant_message: exact normalized match, or an anchored truncation prefix of at least 32 chars ("Done" can never match "Done — details…").last_assistant_messageskips enrichment instead of bypassing correlation.agent_transcript_path).scripts/trace-model-usage-e2e.mjsnow replays each transcript's real final assistant text and selects transcripts with the same live semantics.Validation (real harness, not just unit tests)
--plugin-dir, capture into a staging table: turn 1 row now carriesmodel=claude-fable-5+ full token_usage (was null pre-fix on the same machine/flow); turn 2 carries its own distinct usage (23 vs 83 output tokens matching each reply's length) — no off-by-one.scripts/trace-model-usage-e2e.mjsover real local transcripts: claude 3/3 models with tokens, codex/cursor/pi/openclaw unchanged, PASS.tscclean, full suite green (pre-existingcli-bundle-runtimeenv flake aside).codex reviewof the diff: initial round's 3 blockers (empty-message bypass, unanchored substring matching, e2e harness regression) all addressed as above.Session Context
Session transcript
Summary by CodeRabbit