Skip to content

.NET: [Bug]: In-memory response streams can end before a synthesized terminal event is published #8884

Description

@quifox

Description

InMemoryResponsesService can finish a streaming reader without delivering the terminal event it synthesizes for completion, failure, or cancellation.

The three fallback paths in ExecuteResponseAsync assign a terminal state.Response before calling AddStreamingEvent. A reader can acquire the state lock between those operations, see no new events together with IsTerminal == true, and return false from MoveNextAsync. The terminal event is appended afterward, too late for that reader.

Expected: publishing a synthesized terminal response and its streaming event is atomic from a reader's perspective.

This concerns service-synthesized terminal events. Executor-provided terminal events already go through AddStreamingEvent.

Code Sample

The relevant interleaving is:

  1. The executor finishes without a terminal event, throws, or reports cancellation.
  2. The service assigns the corresponding terminal response snapshot.
  3. Before the service appends the terminal event, a streaming reader takes the state lock and observes an empty event suffix and terminal state.
  4. The reader ends; the service then appends the terminal event.

A controlled service-level regression holds the publication lock while the producer reaches the append, then starts a reader reentrantly. On the current implementation, MoveNextAsync returns false for all three outcomes. These cases reproduced in five consecutive runs. This is a service-level reproduction, not an HTTP/SSE timing test.

Error Messages / Stack Traces

No exception is required: the reader ends without the synthesized response.completed, response.failed, or response.cancelled event.

Package Versions

Microsoft.Agents.AI.Hosting.OpenAI, source at 2d995049b9b08949dd12bebb61236f9300c707c4. The affected .NET code is unchanged from the reproduction baseline 3a837e3272268217fcc8d588700cf6e78fc413ef.

.NET Version

.NET SDK 10.0.401 on Linux x64; affected unit tests also run on .NET 8 and 9.

Additional Context

I'll send a focused fix that keeps each terminal snapshot local until the existing AddStreamingEvent lock publishes it with the event. Regression coverage includes the three terminal outcomes, partial output, independent readers, replay, reader-cancellation isolation, and executor-provided terminal events. No public API or persistence-policy change is needed.

Activity

  1. added
    .NETUsage: [Issues, PRs], Target: .Net
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 30, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Sep 30, 2026
  3. github-actions commented on Sep 30, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    The bug reproduces in dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs::ExecuteResponseAsync, where synthesized terminal snapshots at lines 557-611 become visible before AddStreamingEvent publishes their events. It triggers when a streaming reader acquires ResponseState._lock between those operations and observes IsTerminal with no unread events. Minimal repro: hold the response publication lock, release a gated executor into completed, failed, or cancelled handling, then call MoveNextAsync reentrantly and observe false.

    • Failing test: dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/InMemoryResponsesServiceTerminalEventRaceTests.cs
    • Files examined: dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs, dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/InMemoryResponsesServiceTests.cs, dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/CreateResponse.cs, dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/ResponseSessionRegressionTests.cs
    • Tests run: InMemoryResponsesServiceTerminalEventRaceTests (written; 3/3 failed), InMemoryResponsesServiceTests (3/3 passed)
  4. added
    hostingUsage: [Issues, PRs], Target: all hosting related solutions
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NethostingUsage: [Issues, PRs], Target: all hosting related solutionsreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions