cloudflare / cloudflare/agents
Reused tool-call IDs can overwrite assistant messages during reconciliation
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 711
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
**Describe the bug**
Message reconciliation currently treats `toolCallId` as unique across the whole conversation. Some model providers reuse tool-call IDs across turns.
When a later assistant message contains a reused tool-call ID, it can adopt the ID of an earlier persisted assistant row. The subsequent upsert overwrites that historical row, so messages that appeared correctly while streaming can disappear or become misordered after reloading persisted history.
Terminal tool outputs are also indexed conversation-wide by `toolCallId`, so an older output can be merged into a newer turn that reused the same ID.
The behavior is shared by `agents/chat`, `@cloudflare/ai-chat`, and `@cloudflare/think`.
**To Reproduce**
1. Persist a user message and an assistant tool message with message ID `assistant-first` and tool-call ID `call_reused`.
2. Submit the full transcript again, retaining that first assistant message.
3. Add a later assistant message with a new message ID, such as `assistant-second`, but the same tool-call ID `call_reused`.
4. Read the persisted messages again, as happens on reload.
5. Observe that the later assistant can resolve to the earlier row ID and overwrite it. A stale newer tool part can also receive the older turn's terminal output.
**Expected behavior**
Assistant reconciliation should claim server messages one-to-one:
- exact message IDs claim their server rows first;
- unmatched tool-bearing assistant messages may match only an unclaimed server row sharing a tool-call ID;
- if no unclaimed row matches, the incoming message keeps its own ID;
- terminal tool outputs merge only from the server message with the resolved message ID.
Both turns should remain in persisted history after reload, with their own tool inputs and outputs.
**Screenshots**
Not included; the failure is deterministic at the persisted message-data level.
**Version:**
Current `main` at `413011e5` (`agents` 0.20.1, `@cloudflare/ai-chat` 0.10.1, `@cloudflare/think` 0.15.1).
**Additional context**
A tested patch is already available:
- Comparison: https://github.com/cloudflare/agents/compare/main...jcheese1:agents:fix/reused-tool-call-id-reconciliation?expand=1
- Commit: https://github.com/jcheese1/agents/commit/759bc8dd57b9c22f93400fc9c13f05e878f6048a
Validation completed on that branch:
- shared chat suite: 515 passed;
- AI Chat workers suite: 656 passed;
- Think workers suite: 920 passed, with one unrelated recovery fixture flake that passed in isolation;
- focused Think reconciliation suite: 9 passed;
- Nx affected build: all 21 affected projects passed.
The repository currently limits pull-request creation to collaborators, so the fix is linked here for a collaborator to adopt or open.
Contributor guide
Assessment
This issue has not been assessed yet.