Skip to content

Fix InvalidOperationException in CoalesceWebSearchToolCallContent#7419

Merged
stephentoub merged 3 commits intodotnet:mainfrom
stephentoub:fix/websearch-coalesce-mutation
Mar 20, 2026
Merged

Fix InvalidOperationException in CoalesceWebSearchToolCallContent#7419
stephentoub merged 3 commits intodotnet:mainfrom
stephentoub:fix/websearch-coalesce-mutation

Conversation

@stephentoub
Copy link
Copy Markdown
Member

@stephentoub stephentoub commented Mar 20, 2026

Fix InvalidOperationException in CoalesceWebSearchToolCallContent

Fixes #7414

Problem

CoalesceWebSearchToolCallContent merged streaming updates by mutating the original content objects in-place. In particular, existing.Queries = webSearchCall.Queries made two objects share the same Queries list instance. This is a problem because ToChatResponse can be called multiple times on the same streaming updates: FunctionInvokingChatClient calls it internally to process function calls (line 666), and the caller (e.g. ChatClientAgent from the Agents Framework) calls it again on the same collected updates. On the second call, both content objects now have non-null Queries pointing to the same list, so the foreach-merge path tries to iterate and add to the same list simultaneously, throwing InvalidOperationException: Collection was modified; enumeration operation may not execute.

Fix

Create a new WebSearchToolCallContent with the merged data instead of mutating the originals. This matches the pattern used by all the other coalescing methods (Coalesce<T> for TextContent, CodeInterpreterToolCallContent, etc.), which create new objects via factory delegates. When existing and webSearchCall are the same object instance (a degenerate case), the duplicate is simply removed without creating a new object.

Tests

Added a regression test that mirrors the real-world scenario: streaming updates with an in-progress web search (null Queries), a done web search (populated Queries), and a function call, with ToChatResponse called twice on the same updates. Parameterized over sync/async and whether the first item has queries (exercising both the reference-assignment and list-combining branches).

Microsoft Reviewers: Open in CodeFlow

CoalesceWebSearchToolCallContent merged streaming updates by mutating the
original content objects in-place. In particular, the direct assignment
existing.Queries = webSearchCall.Queries made two objects share the same
Queries list. This is a problem because ToChatResponse can be called
multiple times on the same streaming updates: FunctionInvokingChatClient
calls it internally to process function calls, and the caller (e.g.
ChatClientAgent from the Agents Framework) calls it again on the same
collected updates. On the second call, both content objects have non-null
Queries pointing to the same list, so the foreach-merge path tries to
iterate and add to the same list simultaneously, throwing
InvalidOperationException.

The fix creates a new WebSearchToolCallContent with the merged data
instead of mutating the originals, matching the pattern used by all the
other coalescing methods. This makes ToChatResponse safe to call multiple
times on the same updates.

Fixes dotnet#7414

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@stephentoub stephentoub requested a review from a team as a code owner March 20, 2026 02:36
Copilot AI review requested due to automatic review settings March 20, 2026 02:36
@github-actions github-actions Bot added the area-ai Microsoft.Extensions.AI libraries label Mar 20, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an InvalidOperationException thrown when ToChatResponse is invoked multiple times over the same streaming updates containing WebSearchToolCallContent, by avoiding in-place mutation during coalescing.

Changes:

  • Update CoalesceWebSearchToolCallContent to create a new merged WebSearchToolCallContent instead of mutating existing instances.
  • Add a regression test that calls ToChatResponse twice on the same update sequence (sync/async), covering both “first update has queries” and “first update has null queries” paths.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs Changes web-search coalescing to be non-mutating by replacing the coalesced item with a newly constructed merged instance.
test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatResponseUpdateExtensionsTests.cs Adds a regression test reproducing the multi-ToChatResponse scenario that previously caused the exception.

The old mutating code preserved the first item's Annotations implicitly.
The new code creates a fresh object, so Annotations must be explicitly
carried over.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@stephentoub stephentoub requested a review from jozkee March 20, 2026 02:55
@stephentoub stephentoub enabled auto-merge (squash) March 20, 2026 02:55
Co-authored-by: David Cantú <dacantu@microsoft.com>
@stephentoub stephentoub merged commit 9a8fe6f into dotnet:main Mar 20, 2026
6 checks passed
jozkee pushed a commit to jozkee/extensions that referenced this pull request Apr 3, 2026
…tnet#7419)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-ai Microsoft.Extensions.AI libraries

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed to Coalesce Web Search Tool Call Content

4 participants