Skip to content

🐛 Bug: CopilotChatToolCallsView produces React duplicate-key warning when connect() replays historic tool call events over pre-populated message state #3928

@46ki75

Description

@46ki75

♻️ Reproduction Steps

  1. Use CopilotKit v2 with InMemoryAgentRunner and register at least one frontend tool via useFrontendTool
  2. Send a message that triggers a tool call and let the run complete (the tool call should appear in the chat UI)
  3. Reload the page — or otherwise cause connectAgent() to be called on an agent whose messages state already contains the completed assistant message with the tool call
  4. Open the browser DevTools console

✅ Expected Behavior

No console warnings. Each tool call should appear exactly once in the chat.

❌ Actual Behavior

The browser console shows:

Warning: Encountered two children with the same key, `call_uUF5OZtugHE6tyoC9PPtpAs6`.
Keys should be unique so that components maintain their identity across updates.
Non-unique keys may cause children to be duplicated and/or omitted —
the behavior is unsupported and could change in a future version.
    at CopilotChatToolCallsView
    at MemoizedSlotWrapper
    at CopilotChatAssistantMessage

Root cause: When connectAgent() is called, InMemoryAgentRunner.connect() replays compacted historic events (including TOOL_CALL_START for completed tool calls) via @ag-ui/client's defaultApplyEvents. However, at that point the agent's messages state already contains those tool calls from the prior run. Because defaultApplyEvents pushes to message.toolCalls unconditionally — with no duplicate guard — the same toolCallId ends up in the array twice:

message.toolCalls = [
  { id: "call_abc123", ... },  // ← already in initial state
  { id: "call_abc123", ... },  // ← pushed again by replayed TOOL_CALL_START
]

CopilotChatToolCallsView then maps this array using toolCall.id as a React key, producing the duplicate-key warning.

The existing deduplicateMessages() in CopilotChatMessageView only deduplicates at the message level; it does not dedup entries within each message's toolCalls array.

Fix suggestion: In CopilotChatToolCallsView, deduplicate message.toolCalls by id before rendering:

const uniqueToolCalls = [
  ...new Map(message.toolCalls.map((tc) => [tc.id, tc])).values(),
];

Or equivalently, extend deduplicateMessages() to also dedup within toolCalls arrays.

Note: the underlying idempotency issue in @ag-ui/client's defaultApplyEvents has been separately reported to ag-ui-protocol/ag-ui.

Screenshots

No response

𝌚 CopilotKit Version

❯ pnpm ls | grep "@copilotkit"
├── @copilotkit/a2ui-renderer@1.55.3
├── @copilotkit/react-core@1.55.3

📄 Logs (Optional)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions