cloudflare / cloudflare/agents

Think sends “done” before saving the final transcript

Open Beginner friendly
#2,119 1 comment 0 reactions 0 assignees View on GitHub
think
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

**Describe the bug**
`Think._streamResult()` sends the terminal `cf_agent_use_chat_response` frame with `done: true` before it persists the completed assistant message and broadcasts `cf_agent_chat_messages`.

`useAgentChat` therefore changes to `ready` before the authoritative transcript is available. If the client immediately sends another message, the later transcript broadcast can replace the local message array and temporarily remove the new optimistic user message.

**To Reproduce**
Steps to reproduce the behavior:

1. Create a Think chat using `useAgentChat`.
2. Send a message and wait for its response to finish.
3. Immediately call `sendMessage()` when the chat status changes to `ready`.
4. See the new user message appear optimistically.
5. See it disappear when the previous turn’s delayed `cf_agent_chat_messages` snapshot arrives.
6. See it return after the new turn finishes and another transcript snapshot is broadcast.

**Expected behavior**
Think should persist the completed assistant message and broadcast the authoritative transcript before sending the terminal `done: true` frame.

The completion order should be:

1. `_persistAssistantMessage()`
2. `_broadcastMessages()`
3. Broadcast `done: true`

After `useAgentChat` becomes ready, the client should be able to safely start another turn without a previous-turn snapshot replacing its optimistic message.

**Screenshots**
Not applicable.

**Version:**
Reproduced with `@cloudflare/think@0.15.0` and `agents@0.19.0`. The same ordering is present in `@cloudflare/think@0.15.1`.

**Additional context**
The WebSocket remains connected throughout the reproduction. This is separate from #1983, which concerns buffered messages during reconnect.

A regression test can drive `_streamResult()` with a completed UI message stream and assert the event order:

```text
persist → transcript broadcast → done
```
Moving persistence and _broadcastMessages() before the terminal frame resolves the issue without requiring client-side message reconciliation.

Contributor guide

Open the contributing guide

Research direction

Start at Think._streamResult() and inspect its calls to _persistAssistantMessage() and _broadcastMessages(), along with the terminal done frame. Drive _streamResult() with a completed UI message stream and assert that the observed order is persist, transcript broadcast, then done. Done means the terminal frame is sent only after the transcript broadcast.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend-api-design
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.