Skip to content

fix(server): key assistant text blocks by message id instead of stream position - #7143

Open
Kiri110K wants to merge 2 commits into
pingdotgg:mainfrom
Kiri110K:fix/claude-adapter-message-scoped-blocks
Open

fix(server): key assistant text blocks by message id instead of stream position#7143
Kiri110K wants to merge 2 commits into
pingdotgg:mainfrom
Kiri110K:fix/claude-adapter-message-scoped-blocks

Conversation

@Kiri110K

@Kiri110K Kiri110K commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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

ClaudeAdapter tracked assistant text blocks in one turn-long list and matched snapshot text to blocks by list position. A claude/assistant snapshot 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

  • A stalled stream truncates the message to its first delta — the incident in [Bug]: A stalled text stream silently truncates the assistant message to its first delta #7137: one text_delta (P), 3m16s of silence, no content_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.
  • Multi-text-block messages (text → tool call → text) could backfill the wrong block, since position-in-turn and position-in-message disagree.
  • Two consecutive messages merged into one when the second message restarted block indexes at zero while the first was still open.
  • A subagent's content_block_stop closed the parent's open block, truncating the parent message mid-stream.

Worth noting for review: open PR #6429 copies the positional scheme into new backfillReasoningBlocksFromSnapshot for thinking blocks. If it lands on top of this PR, reasoning gets the message-scoped base instead of inheriting the same truncation bug.

Tests

  • Four new regression tests, one per defect above, each replaying the SDK event sequence; all four fail without the fix and pass with it.
  • Full server suite: 230 files passed, 2510 tests passed, 7 skipped, on top of current main.

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
ClaudeAdapter now scopes assistant text blocks by API message.id (from message_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/assistant snapshot, backfill targets the correct message’s block and emits only the unseen text suffix as content.delta (tracked via streamedText), 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_stop from 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

  • Assistant text blocks in ClaudeAdapter.ts are now keyed by both block index and API message ID, preventing content from different API messages being merged into the same chat item.
  • Snapshot-based stream repair now emits only the unseen suffix of fallback text, preventing duplicate text from appearing on clients when a stalled stream is repaired.
  • Subagent content_block_stop events no longer truncate the parent assistant message; subagent traffic is filtered by checking for a parent_tool_use_id.
  • message_start events capture the current assistant message ID into turn state so subsequent content blocks are correctly associated with their parent message.
  • Five new integration tests cover streaming repair, multi-block messages, index reuse across messages, and subagent event isolation.

Macroscope summarized d14de2d.

…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>
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7622e3ef-7768-483a-9103-5b59798ba62c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread apps/server/src/provider/Layers/ClaudeAdapter.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant