Repository navigation
Watcher: don't consume half-written transcript lines - #291
Conversation
The live watcher drain (DrainNewLines) read the transcript with ReadLineAsync, which returns a final line that is not yet newline-terminated — i.e. the agent is mid-write of it. It sent the truncated prefix and advanced its watermark past it, so the completed line was never re-read (its truncated JSON fails to normalize server-side, and the gap can't be back-filled below the high-water mark). Large Read tool_result lines were the common victim, surfacing as orphaned tool calls that spin forever in chat (AI-1243). Add SplitNewCompleteLines, which emits only newline-terminated lines and holds a partial final line back — excluded from the batch AND the watermark — until a later drain sees it complete. The final drain at session end opts out (holdIncompleteFinalLine: false) so a genuinely unterminated last line is still delivered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoWatcher: avoid consuming non-newline-terminated transcript lines
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
… read (Qodo #291) [AI-1243] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem
The live watcher's 1-second drain (
WatchCommand.DrainNewLines) reads the transcript to EOF withStreamReader.ReadLineAsync, which returns the final line even when it is not yet newline-terminated — i.e. the agent is mid-write of it. The watcher sends that truncated prefix and advancesstate.LinesProcessedpast it, so when the writer completes the line and appends more, the completed line is never re-read. Its truncated JSON fails to normalize server-side, so the event is permanently lost (and the gap sits below the high-water mark, where a resend can't back-fill it).Large
Readtool_result lines (long JSON, slow to flush) are the common victim — they land as orphaned tool calls that render as an eternal spinner in the Capacitor chat view.Diagnosed from a real hosted-subagent session: two
Readresults present in the source.jsonlwere absent from the ingested stream, with matching$lineNumbergaps at exactly those tool_result lines.Fix
SplitNewCompleteLines(fileText, linesProcessed, holdIncompleteFinalLine = true):holdIncompleteFinalLine: false(the file is static then), so a vendor that leaves its last line unterminated is still delivered.Blank-line skipping and line-number semantics are unchanged.
Tests
SplitNewCompleteLinesTests— 10 cases incl. the exact bug shape (tool_use complete + tool_result mid-write → held back, then delivered once the line completes). Full CLI unit suite green (2481); AOT publish clean (no IL2026/IL3050).Linear: AI-1243. Paired with the server-side chat-rendering guard (kurrent-io/kcap-server) that stops already-orphaned calls from spinning.
🤖 Generated with Claude Code