Skip to content

Request content: keep the live answer until the stored one has landed - #334

Merged
fylorn merged 1 commit into
devfrom
fix/live-content-handoff
Oct 14, 2026
Merged

fylorn merged 1 commit into
devfrom
fix/live-content-handoff

Conversation

@fylorn

@fylorn fylorn commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Problem

In the request drawer's Content tab, a streamed answer types out live as expected, but right after the request finishes the answer turns into "not saved" and goes blank. Reopening the request shows the full answer.

Cause

Core records the request row and the bodies on separate threads (tw_store::task). The row and request_priced land first. The request body was stored when the request started, while the heads and the answer are written only after the request ends and has been redacted (Ending::settle → bodies channel → for_disk → bodies thread).

landed(d) accepted the stored detail as soon as any of heads / request body / response body was present, so the request body alone was enough. The drawer switched to a detail with no response yet and nothing fetched it again.

A second path made it stick: the live subscription was dropped as soon as the request left in-flight. request_priced can arrive before the last chunk and end, so the subscription stopped before end, and the retry loop that fetches the stored copy (settle) never ran.

Fix

  • caughtUp(d, live) replaces landed(d): the stored detail counts only once it has every head and body the live content showed: heads, client request, the serving hop's request when it was sent separately, the client answer, and the upstream answer when it differs from what the client got (until the client copy lands, response_body is the upstream one). Without live content the old rule applies.
  • useLiveContent keeps the subscription until it closes on its own (end, error, or not running) instead of stopping when the request leaves in-flight; it still unsubscribes when the drawer closes or switches request. onClosed gets the live content so settle can keep polling until the stored copy has caught up.

Verification

  • pnpm typecheck, pnpm vitest run (1206 tests pass), with 5 new cases for caughtUp in dialect.test.ts.
  • Reproduced with a mock that follows core's order at the end (row + request_priced first, last chunk + end 250 ms later, heads + answer on disk 2 s later). Before the fix the answer section went to "not saved" at the end of the stream; after it the answer stays and the drawer switches to the stored detail once it has landed. The default order (end before request_priced) is unchanged.

🤖 Generated with Claude Code

When a streamed request ended, the Content tab could switch from the live
answer to the stored detail before the answer was on disk and show the
response as not saved; reopening the request showed it in full.

Core records the request row and the bodies on separate threads. The row
(and `request_priced`) lands first; the request body was stored when the
request started, while the heads and the answer are written after the
request ends and has been redacted. `landed()` accepted the detail as soon
as any one of them was there, so the request body alone was enough.

- `caughtUp(d, live)` replaces `landed(d)`: the stored detail counts only
  once it has every head and body the live content showed (heads, client
  request, the serving hop's request, the client answer, and the upstream
  answer when it differs from what the client got)
- The live subscription now runs until it closes on its own instead of
  stopping when the request leaves in-flight: `request_priced` can arrive
  before the last chunk and `end`, and stopping then lost the tail of the
  answer and never fetched the stored copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn
fylorn merged commit fc9899d into dev Oct 14, 2026
6 checks passed
@fylorn
fylorn deleted the fix/live-content-handoff branch October 14, 2026 03:26
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant