fix(server): Claude answers stream as they are written#24410

open7
main
fix/issue-17270
opened1d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 1d ago

Problem

Claude assistant replies appeared only when the SDK delivered a completed text block, even with Response streaming set to Show finished paragraphs. Reproduced in the Linux web client by asking Claude Opus 5.5 for five paragraphs about the history of the printing press: reasoning appeared during generation, but the reply arrived together at completion.

Change

Handle Claude text block starts and text deltas with the existing provider text coalescer. Reconcile completed snapshots with the streamed block identity, and flush partial text when a turn stops so completion and fallback paths do not duplicate the reply.

Scope and approval

Fixes https://github.com/pingdotgg/t3code/issues/17270

GitCafe: #17270

Triaged bug via the automated issue flow.

Verification

  • Red regression: no assistant update existed before the completed snapshot (expected [] to include 'Movable type changed Europe.').
  • Final Claude and Codex adapter suites: 298 tests passed. Related replay, delivery, coalescing, provider-switch, and thread-fork suites: 90 tests passed. Server typecheck passed.
  • Real Linux web client with Claude Opus 5.5: paragraphs appeared progressively while the turn was running; the first appeared about eight seconds after submission.

Final post-merge verification: 300 scoped tests passed. Both fresh independent code reviews returned MERGE on the final revision. The capture revision was followed by focused protocol tests and a three-line correction preserving result fallback after an entirely empty stream and snapshot.

Real Claude recordings: Before, exact five-paragraph prompt, After, same prompt. The after run shows successive paragraphs at roughly 8, 10, 12, and 14 seconds, before completion at 19 seconds. Live prose varies; the deterministic replay below supplies matched visual comparisons.

Additional observed behavior: partial streamed text survives Stop. The synthetic mixed-block snapshot groups streamed and snapshot-only text into separate rows; the SDK normally sends one snapshot per content block. These differences are shown in the evidence index rather than claimed as visual equality.

Rendering environment: collaborative Chromium browser on Linux, no device emulation, CSS viewports 1440×900 and 390×900 in light/dark, DPR 2, default font scaling. The browser tool caps desktop rasters at 1280×800; narrow rasters are 780×1800; the manifest records measured CSS and raster dimensions per frame. Web was exercised; Windows desktop and native clients were not separately run. No client code or contracts change.

Full evidence index, narrated chapters, seeds and replay drivers. Protocol-only terminal, empty-response, child-stream and unknown-index variants are covered by focused adapter tests. The follow-up retention replay below uses unique session IDs; the original captures establish point-in-time rendering.

Tested 096782c3e1 against 27e0c684c5 using a sanitized SDK replay through the real Claude SDK, adapter, server and web app. Both paragraph and turn modes are covered. SDK normally emits per-content-block snapshots; mixed-block grouping is fixture-specific.

The eight initial empty-state pairs are pixel-identical at threshold zero. Head shows completed paragraphs before snapshots; turn mode waits. Correction/repetition, thinking/tool/new message, actual UI Stop in both modes, and clean snapshot-only follow-up are captured. Counter/icon noise in turn diffT and fixture-specific settled grouping differences are disclosed in the index. Focused identity/empty/error/child-stream variants are proved by adapter tests, rather than visual captures.

Exhaustive manifest. Exhaustive index.

Both diff columns compare exactly the displayed Before/After gate pair. Diff0 threshold0/255; DiffT threshold16/255; maximum absolute RGB channel difference, amplified4×, no masks. All68 accepted matched milestone pairs have both threshold diffs in the index. Initial empty-state equality is separate.

Follow-up isolation and compatibility proof: 49 retention/reconciliation assertions and five fallback assertions passed. Native IDs are unique per session and identical across matched baseline/head runs. Text, ownership, IDs and order survive reload and a later fixture thread. The final empty-stream/snapshot fallback was also exercised through the real app in head paragraph mode. Supplemental retention recordings cover 1440/light in both modes; protocol-only variants use the adapter tests.

Baseline left and fixed app right after another fixture thread and reload; paragraph above, turn below

Independent final proof audit: SUFFICIENT for all six matrix rows.

Models: Claude Opus 5.5 and GPT-6.1-Sol. Harnesses: Claude Agent SDK and Codex, orchestrated through T3 Code.

Checks

All checks passed

Added 7 commits

  • fix(server): Claude answers stream as they are written096782c
  • Merge main into fix/issue-1727019ed4c9
  • fix(server): empty streamed Claude text keeps the result fallback46255de
  • test(server): cover Claude text stream settlement and suppression paths6f7296e
  • Merge remote-tracking branch 'origin/main' into fix/issue-17270a459583
  • test(server): reproduces #17270 empty fallbackdcb52e6
  • test(server): Claude assistant text streams before the final snapshote22e3b7
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This change materially alters Claude's existing response path by emitting progressive assistant content and reconciling it across snapshots, completion, and interruption flows. The stateful streaming and lifecycle handling is focused but substantial enough to merit human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire4.9 KiB4.9 KiB0 B (0.0%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
CodexLive turn WebSocket decoded20.8 KiB20.8 KiB0 B (0.0%)29.3 KiB✅
CodexLive turn messages110 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB−31 B (−0.6%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB−31 B (−2.5%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB−41 B (−0.2%)29.3 KiB✅
ClaudeLive turn messages21−1 (−50.0%)8✅

Baseline: 6e79211 · PR result: a459583 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 6e792117436662208bf2b6eb71ee8d0d88817e43 and a459583c71f7edee50f5ec0724db712f771398ed.

External image Files selected for processing: 2
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts

Limit details: You’ve used all 10 included reviews currently available.

Walkthrough

ClaudeAdapterV2 now emits assistant text as it arrives in SDK stream events. It tracks streamed blocks, settles them at turn completion, and reconciles them with completed assistant snapshots. Tests cover streamed text, snapshot handling, and empty or child-thread-only frames.

Changes: Claude assistant text streaming

Layer / File(s)Summary
Streaming artifact state
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Assistant nodes, messages, and turn items now expose running streaming state and use the streamed block’s start time. Active turns track streamed text blocks and their timing and order.
Streamed text updates
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Root text-block events create and update assistant text items. A coalescer emits node, message, and turn-item updates, and turn finalization flushes pending text.
Snapshot reconciliation and validation
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Snapshots complete matching streamed blocks and project unstreamed text. Tests cover snapshot correction, turn completion without a snapshot, result-only text, and empty or child-thread-only frames.

Priority: External image Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant SDK as Claude SDK
3 participant Adapter as ClaudeAdapterV2
4 participant Stream as Orchestration event stream
5 SDK->>Adapter: Root text-block start and text-delta events
6 Adapter->>Stream: Running assistant node, message, and turn-item updates
7 SDK->>Adapter: Assistant snapshot
8 Adapter->>Stream: Completed streamed items and unstreamed snapshot text

Suggested reviewers: t3dotgg, macroscopeapp

Merge Risk: External image Minimal · up to a4595

Claude text can appear while the response is still being written. No merge-blocking issue was established.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Description checkPassedThe description includes the required Problem, Change, Scope and approval, and Verification sections. It identifies the linked issue, explains the implementation, reports focused test results, documen…
Title checkPassedThe title clearly and concisely describes the primary change: Claude answers now stream as they are written. It uses an appropriate conventional commit format.
Linked Issues checkPassedIssue #17270 requires Claude assistant text to appear incrementally in paragraph mode. The PR adds Claude SDK text-block start and delta projection through the provider text coalescer. It reconciles s…
Out of Scope Changes checkPassedThe reported changes are limited to ClaudeAdapterV2 and its focused test file. The implementation supports the linked streaming fix, and the tests verify its delivery, reconciliation, fallback, and te…
Full details: Description check

Explanation

The description includes the required Problem, Change, Scope and approval, and Verification sections. It identifies the linked issue, explains the implementation, reports focused test results, documents manual verification and limitations, and ends with the model and harness information.

Full details: Linked Issues check

Explanation

Issue #17270 requires Claude assistant text to appear incrementally in paragraph mode. The PR adds Claude SDK text-block start and delta projection through the provider text coalescer. It reconciles streamed blocks with completed snapshots and settles partial text on success, error, query exit, and interruption. The added ClaudeAdapterV2 tests cover streamed updates, snapshot correction, empty snapshots, fallback output, and child-thread or empty-stream cases. These changes address the issue's coding and automated-test requirements.

Full details: Out of Scope Changes check

Explanation

The reported changes are limited to ClaudeAdapterV2 and its focused test file. The implementation supports the linked streaming fix, and the tests verify its delivery, reconciliation, fallback, and termination behavior. No unrelated change is established by the supplied change summary. The repository diff was unavailable, so this assessment relies on the supplied PR evidence and the reviewed file evidence.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.

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