fix(server): key assistant text blocks by message id instead of stream position - #7143
fix(server): key assistant text blocks by message id instead of stream position#7143Kiri110K wants to merge 2 commits into
Conversation
…m position The adapter tracked assistant text blocks in one turn-long list and matched snapshot text to blocks by list position. Any turn with more than one assistant message could mismatch: a claude/assistant snapshot describes one message, not the whole turn. Read message_start (previously ignored) and key each text block by its message id plus the block index within that message. Snapshot backfill now only touches its own message's blocks, and a block whose stream was cut mid-message is completed from the snapshot when the snapshot text extends the streamed prefix. This fixes four defects with one root cause: - a stalled stream truncating the message to its first delta (pingdotgg#7137) - snapshots landing on the wrong block in multi-text-block messages - two consecutive messages merging when block indexes restart at zero - a subagent content_block_stop closing the parent's open block Fixes pingdotgg#7137 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
ApprovabilityVerdict: Needs human review This bug fix modifies core streaming state machine logic with non-trivial changes to how text blocks are associated with messages during real-time streaming. The complex matching logic and multiple fallback paths warrant human review despite thorough test coverage. You can customize Macroscope's approvability policy. Learn more. |
…t-only message Macroscope on the PR found that the id-less fallback in backfillAssistantTextBlocksFromSnapshot could resolve to a COMPLETED block at the same content index; completionEmitted then skips the message entirely, so an assistant message that never streamed loses its text. Exclude completed blocks from the id-less fallback unless their delivered text matches the snapshot — the bare exclusion Macroscope suggested would instead re-synthesize the ordinary stream -> stop -> snapshot flow as a duplicate whenever message ids are absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deeper of two alternative fixes for #7137 — the small one is #7142. Pick one, they are not meant to be merged together. This one changes how assistant text blocks are identified; #7142 keeps positional matching and just repairs its alignment.
What changed
ClaudeAdaptertracked assistant text blocks in one turn-long list and matched snapshot text to blocks by list position. Aclaude/assistantsnapshot describes one message, not the whole turn, so any turn with more than one assistant message could mismatch.The adapter now reads
message_start— previously ignored entirely — and keys every text block by its message id plus the block index within that message. Snapshot backfill resolves blocks of its own message only. A block whose stream was cut mid-message is completed from the snapshot when the snapshot text extends the streamed prefix; a snapshot that contradicts already-shown text is never written over it (warning logged instead).Why this fixes four defects with one root cause
text_delta(P), 3m16s of silence, nocontent_block_stop, full 2.4 KB snapshot at the end. Positional matching wrote the snapshot onto an earlier completed block and the real block was force-completed empty.content_block_stopclosed the parent's open block, truncating the parent message mid-stream.Worth noting for review: open PR #6429 copies the positional scheme into new
backfillReasoningBlocksFromSnapshotfor thinking blocks. If it lands on top of this PR, reasoning gets the message-scoped base instead of inheriting the same truncation bug.Tests
Diff is +147/−40 source; the rest is tests.
🤖 Generated with Claude Code
Note
Medium Risk
Changes core Claude streaming-to-UI mapping for assistant messages; wrong block association would still truncate or duplicate chat text, though behavior is heavily regression-tested.
Overview
ClaudeAdapternow scopes assistant text blocks by APImessage.id(frommessage_start) plus content-block index, instead of matching snapshot text to blocks by position in a turn-wide list.When a stream stalls and the CLI delivers a full
claude/assistantsnapshot, backfill targets the correct message’s block and emits only the unseen text suffix ascontent.delta(tracked viastreamedText), avoiding truncated or duplicated assistant messages. Snapshot matching no longer completes blocks on snapshot alone unless the stream already closed; divergent snapshot text is skipped with a warning.content_block_stopfrom subagent traffic no longer closes the parent’s open text block. Synthetic snapshot-only messages use content index + message id instead of negative synthetic block indices.Five new regression tests replay stalled streams, multi-block repair, reused block indexes across messages, subagent stops, and snapshot-only second messages.
Reviewed by Cursor Bugbot for commit d14de2d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix assistant text blocks to key by message ID instead of stream position in ClaudeAdapter
ClaudeAdapter.tsare now keyed by both block index and API message ID, preventing content from different API messages being merged into the same chat item.content_block_stopevents no longer truncate the parent assistant message; subagent traffic is filtered by checking for aparent_tool_use_id.message_startevents capture the current assistant message ID into turn state so subsequent content blocks are correctly associated with their parent message.Macroscope summarized d14de2d.