# fix\(server\): Claude answers stream as they are written · PR \#24410 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/24410)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: pingdotgg/t3code

Source branch: fix/issue-17270

Target branch: main

Head commit: a459583c71f7edee50f5ec0724db712f771398ed

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/24410)

[Commits](https://git.cafe/pingdotgg/t3code/pull/24410/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/24410/diffs?format=markdown)

## Description

---

## 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](https://git.cafe/pingdotgg/t3code/issues/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](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/8504f5a3e3be0a1c/issue-17270-final-tests.log). 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](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7e8bb559cb67dc95/mcp-launch-client-3a325e80fb-9483-4e3f-90ea-1a0d6c56054d-fwd-3a61aa2f8ace735e1b6-84853e73-c57d-4fc5-bdf5-19fe8cae6a83-mp4.mp4), [After, same prompt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c0e7b46bb51af262/mcp-launch-client-3a325e80fb-9483-4e3f-90ea-1a0d6c56054d-fwd-3a61aa2f8ace735e1b6-066301d8-ef96-4b0a-a497-2e256c7220c0-mp4.mp4). 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](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/745ec28ac885dec6/index.md). 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](https://git.cafe/pingdotgg/t3code/commit/096782c3e19f3a58997c0f209b01e29d1b476f47) against [27e0c684c5](https://git.cafe/pingdotgg/t3code/commit/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.

| Config | Before (paragraph gate)<br>[27e0c684c5](https://git.cafe/pingdotgg/t3code/commit/27e0c684c5) | After (paragraph gate)<br>[096782c3e1](https://git.cafe/pingdotgg/t3code/commit/096782c3e19f3a58997c0f209b01e29d1b476f47) | Diff0 (0/255,4×) | DiffT (16/255,4×) | Recordings |
|---|---|---|---|---|---|
| 1440-light | ![Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d7a890de87049406/1440-light-clean-paragraph-before-diffT.png) | ![After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4a401ec3e2d10219/1440-light-clean-paragraph-after-diffT.png) | ![Diff0 same gate pair](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fc7f6931b8ecc3f7/1440-light-clean-paragraph-diffT-threshold0-amplified4.png) | ![DiffT same gate pair threshold16](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/90cd60dbed82e22c/1440-light-clean-paragraph-diffT-threshold16-amplified4.png) | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/eb5119eaab2d1fe8/1440-light-clean-before-raw.mp4) / [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7e39d3c6f34647b6/1440-light-clean-after-raw.mp4) |
| 1440-dark | ![Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e7bfbdd5dbcb140b/1440-dark-clean-paragraph-before-diffT.png) | ![After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fef6c0d8c82b49a9/1440-dark-clean-paragraph-after-diffT.png) | ![Diff0 same gate pair](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d6783afa86aa5e8f/1440-dark-clean-paragraph-diffT-threshold0-amplified4.png) | ![DiffT same gate pair threshold16](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/aa506998a8c8a4a4/1440-dark-clean-paragraph-diffT-threshold16-amplified4.png) | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/59ed1ad56e1ce64b/1440-dark-clean-before-raw.mp4) / [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4c5e7a676b912f47/1440-dark-clean-after-raw.mp4) |
| 390-light | ![Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/653b136df533d022/390-light-clean-paragraph-before-diffT.png) | ![After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b796d36f3d9c54c7/390-light-clean-paragraph-after-diffT.png) | ![Diff0 same gate pair](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/57fa67b88c83e6d8/390-light-clean-paragraph-diffT-threshold0-amplified4.png) | ![DiffT same gate pair threshold16](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b69b45f7a77d6381/390-light-clean-paragraph-diffT-threshold16-amplified4.png) | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e0e4c33a72d6cda1/390-light-clean-before-main-only.mp4) / [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3ae7e9048ddc7604/390-light-clean-after-main-only.mp4); Stop [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/47dd073bdd764b1b/390-light-cancel-before-raw.mp4) / [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/44d2b07233bd28e7/390-light-cancel-after-raw.mp4) |
| 390-dark | ![Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9d656a96dc91a8fb/390-dark-clean-paragraph-before-diffT.png) | ![After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/715333b39fc601a2/390-dark-clean-paragraph-after-diffT.png) | ![Diff0 same gate pair](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d0ee25a84b330f68/390-dark-clean-paragraph-diffT-threshold0-amplified4.png) | ![DiffT same gate pair threshold16](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/37bc31269bcc777c/390-dark-clean-paragraph-diffT-threshold16-amplified4.png) | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a8d905ff9a5f7eec/390-dark-clean-before-main-only.mp4) / [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/891366ac69db9012/390-dark-clean-after-main-only.mp4); Stop [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1a3913c5fc3c64ea/390-dark-cancel-before-raw.mp4) / [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/0804dc39eddec894/390-dark-cancel-after-raw.mp4) |

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](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/146b3a3ec722b59c/evidence-manifest.json). [Exhaustive index](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/745ec28ac885dec6/index.md).

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](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/47246fb7ac3d6c6d/followup-report.md). 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](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2108af6ce1d57858/ready-reload-comparison.png)

[Independent final proof audit: SUFFICIENT for all six matrix rows](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/17c0217e1fb6fc54/pass2-followup-audit.md).

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

