Skip to content

Stream resumption loses reasoning/thinking state after page refresh #896

Closed

Description

@alexanderjacobsen

Problem

When a page is refreshed during an active stream that includes reasoning/thinking (e.g., OpenAI o-series or GPT models with thinking enabled), the "thinking" state is lost after reconnect. The text response continues correctly via stream resumption, but reasoning parts are no longer visible.

Root cause

_sendStreamChunks replays all stored chunks synchronously in a tight loop:

for (const chunk of chunks || []) {
  connection.send(JSON.stringify({...}));
}

On the client, each chunk triggers a setMessages call. React batches all these state updates and only renders the final state. By that time, reasoning-end has already been processed, so reasoning parts have state: 'done' and are never visible to the user — even though the model may still be actively thinking.

During the original stream this isn't an issue because chunks arrive gradually (as the LLM generates tokens), giving React time to render intermediate states like "Thinking...".

Secondary issue

There's also a race condition between onConnect (which sends CF_AGENT_STREAM_RESUMING) and the useEffect in useAgentChat that registers the message handler. If the WebSocket connects before the handler is registered, the notification is lost and stream resumption doesn't happen at all.

Suggested fix directions

  1. Expose a "replaying" flag so the client knows chunks are being replayed and can preserve intermediate states
  2. Send chunks with microtask yields between them so React can render intermediate states
  3. For the race condition: have the client request stream resumption after the handler is ready, rather than relying solely on the server pushing it in onConnect

Activity

  1. threepointone commented on Feb 12, 2026

    @threepointone
    Contributor

    I'll fix this, thanks for the report

  2. whoiskatrin commented on Feb 13, 2026

    @whoiskatrin
    Contributor

    @alexanderjacobsen now this is the issue that should be fixed in the attached PR

  3. threepointone commented on Feb 14, 2026

    @threepointone
    Contributor

    This is fixed in main, should go out this coming week.

  4. alexanderjacobsen commented on Feb 25, 2026

    @alexanderjacobsen
    ContributorAuthor

    This isn't fully fixed on @cloudflare/ai-chat@0.1.4. The race condition fix works, but active streams still lose state after reconnect.

    After replayChunks() sends all stored chunks with replay: true, there's no flush to React state for active streams. flushActiveStreamToMessages only runs when data.done is true during replay, but for active streams done is never sent after replay. The replayed chunks sit in activeStreamRef unflushed until the next live chunk arrives.

    Result: After refreshing during a thinking phase, messages contains only the user message (no assistant message), status stays ready, and the UI shows nothing until the model finishes thinking and the first text token arrives as a live chunk.

    The server should signal replay completion for active streams so the client can flush the accumulated state.

    cc @threepointone

  5. threepointone commented on Feb 25, 2026

    @threepointone
    Contributor

    fixing

  6. added 2 commits that reference this issue on Feb 25, 2026
    38941e4
    aec54c0
  7. threepointone commented on Feb 25, 2026

    @threepointone
    Contributor

    try this and tell me if it works for you? npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/ai-chat@989

  8. added a commit that references this issue on Feb 26, 2026
    8404954
  9. alexanderjacobsen commented on Feb 26, 2026

    @alexanderjacobsen
    ContributorAuthor

    @threepointone Tested npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/ai-chat@989 — the replayComplete flush works (assistant message now appears after refresh), but status from useChat stays "ready" during stream resumption.

    The resumed chunks flow through the raw onAgentMessage WebSocket handler, which calls flushActiveStreamToMessages → setMessages. But the AI SDK's useChat transport is not involved, so its status never transitions to "streaming".

    In our case this means after refresh during an active stream:

    • Messages show correctly (user + partial assistant) ✅
    • status stays "ready" instead of "streaming" ❌
    • isLoading is false → no abort button, no thinking indicator
    • UI appears idle even though the model is still generating

    The transport's reconnectToStream returns null, so useChat has no way to know about the resumed stream. Possible fixes:

    1. Have reconnectToStream return a ReadableStream that receives the replayed + live chunks
    2. Expose a setStatus override so onAgentMessage can force "streaming" during replay
  10. threepointone commented on Feb 26, 2026

    @threepointone
    Contributor

    I'll have a look

  11. threepointone commented on Feb 26, 2026

    @threepointone
    Contributor

    Here's another go: npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/ai-chat@999

  12. alexanderjacobsen commented on Feb 26, 2026

    @alexanderjacobsen
    ContributorAuthor

    Tested PR #999 — status still stays "ready" after page refresh during an active stream.

    Root cause confirmed with debug logging: reconnectToStream is called while the PartySocket readyState is 3 (CLOSED). The onDetect listener is registered on the old (dead) internal WebSocket. By the time PartySocket reconnects and the server responds, the 3s timeout has already cleaned up the listener.

    Reopened #1000 with details and console output.

  13. added a commit that references this issue on Feb 26, 2026
    2aa8c56
  14. threepointone commented on Feb 27, 2026

    @threepointone
    Contributor

    Closing this issue for #1000

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions