Skip to content

Copilot workspace conversations regularly dying mid stream #375

Description

@ThomCogworks

Which component is this issue related to?

Umbraco.AI.Agent.Copilot

Which Umbraco AI version are you using? (Please write the exact version, example: 10.1.0)

17.4.0-rc.3

Bug summary

StreamAgentAGUI throws an unhandled InvalidOperationException ("Scope X being disposed is not the Ambient Scope Y") from inside EFCoreAIConversationRepository.GetSessionStateJsonAsync while restoring a conversation's session state — and it happens after the SSE response has already started writing to the client. The result: the chat stream dies silently mid-reply with no error event ever reaching the browser. The conversation appears to "hang," and sending a follow-up message causes the client to reconnect and (we suspect, see below) replay/duplicate the prior transcript rather than cleanly resuming it.

Specifics

Environment:

  • Umbraco.AI: 17.4.0-rc.3
  • Umbraco.AI.Agent: 17.2.0-rc.3
  • Umbraco.AI.Agent.Copilot: 17.1.0-rc.3
  • Umbraco.AI.Agent.Copilot.Workspace: 17.0.0-rc.3
  • Umbraco.AI.Agent.UI: 17.1.0-rc.3
  • Umbraco.Cms: 17.5.3
  • Provider: Anthropic, model claude-sonnet-5
  • Persistence: Umbraco.AI.Agent.Conversations.Persistence (EF Core / SQL Server)
  • Reproduced with rc.2 and rc.3 of the whole Copilot Workspace chain — upgrading rc.2 → rc.3 did not resolve it.

What we observe from the user's side

  • A Copilot chat conversation streams a response, then goes silent mid-sentence/mid-table with no error shown.
  • Sending a nudge message (e.g. "hello") sometimes appears to make it "pick back up," but closer inspection of the raw prompt logged in the Umbraco AI activity log shows the entire prior transcript being duplicated into the next request rather than cleanly continued — including identical tool_use IDs appearing twice, with the second copy of each tool result re-serialized in a different shape (PascalCase, JSON-encoded as a string) than the original (camelCase, native object).
  • Eventually the duplication/ordering issue is severe enough that the Anthropic API rejects the request outright with a generic "Error: The request was rejected by the AI service."

Server-side exception (the actual root cause)

[14:05:49 WRN] The response has already started, the error handler will not be executed.
[14:05:49 ERR] An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The Scope f6437b5e-4697-498e-bcc5-13fc3c81e336 being disposed is not the Ambient Scope c51b4d1d-0d36-4c6f-8590-41d14054295b. This typically indicates that a child Scope was not disposed, or flowed to a child thread that was not awaited, or concurrent threads are accessing the same Scope (Ambient context) which is not supported. If using Task.Run (or similar) as a fire and forget tasks or to run threads in parallel you must suppress execution context flow with ExecutionContext.SuppressFlow() and ExecutionContext.RestoreFlow().
   at Umbraco.Cms.Infrastructure.Scoping.Scope.Dispose()
   at Umbraco.Cms.Persistence.EFCore.Scoping.EFCoreScope`1.Dispose()
   at Umbraco.AI.Agent.Conversations.Persistence.Conversations.EFCoreAIConversationRepository.GetSessionStateJsonAsync(Guid id, CancellationToken cancellationToken)
   at Umbraco.AI.Agent.Conversations.Persistence.Conversations.EFCoreAIConversationRepository.GetSessionStateJsonAsync(Guid id, CancellationToken cancellationToken)
   at Umbraco.AI.Agent.Conversations.Core.Conversations.ConversationChatHistoryProvider.GetSessionStateAsync(Guid conversationId, CancellationToken cancellationToken)
   at Umbraco.AI.Agent.Copilot.Workspace.Web.Api.Management.Stream.Controllers.StreamConversationAGUIController.<>c__DisplayClass6_0.<<StreamAgentAGUI>b__3>d.MoveNext()
   --- End of stack trace from previous location ---
   at Umbraco.AI.Agent.Core.Agents.AIAgentService.CreateOrRestoreSessionAsync(AIAgent mafAgent, AIConversationHistoryBinding historyBinding, Task`1 sessionStateTask, CancellationToken cancellationToken)
   at Umbraco.AI.Agent.Core.Agents.AIAgentService.StreamAgentAGUIAsync(Guid agentId, AGUIRunRequest request, IEnumerable`1 frontendTools, AIAgentExecutionOptions options, CancellationToken cancellationToken)
   at Umbraco.AI.AGUI.Streaming.AGUIStreamResult.ExecuteAsync(HttpContext httpContext)

Immediately following, on reconnect:

[14:05:52 INF] Auto-denying stale approval request for callId toolu_019kHKxiADzsBipWhA6awDrh -- Auto-denied: the browser was reloaded before this action was approved or denied.
[14:05:52 INF] Auto-denying stale approval request for callId toolu_01NSeSJsqy5sjt9DPTwSZceR -- Auto-denied: the browser was reloaded before this action was approved or denied.
[14:05:52 WRN] File file-74311052b8624aa49bfe4b709ca007d9 not found for thread eea2b0b9-abb5-4765-9d6b-7cbecaf0323e

GetSessionStateJsonAsync opens an EF Core scope to read the persisted conversation state. Umbraco's Scope/IScopeProvider tracks the "ambient" scope via AsyncLocal, which requires the logical execution context to flow correctly across awaits. AGUIStreamResult.ExecuteAsync streams the response as an IAsyncEnumerable/async sequence written incrementally to the HTTP response — if the continuation after a yield/write resumes on a different logical execution context (e.g. due to how the streaming write pump schedules continuations), the ambient scope tracked via AsyncLocal can become inconsistent with the scope actually being disposed, producing exactly this "Scope X is not the Ambient Scope Y" error.

Because it happens after the response has already started (per the "response has already started" warning), ASP.NET Core can't turn it into a clean HTTP error — the connection just stops, with no SSE error event, which is what surfaces to the user as the chat silently going quiet mid-reply.

We also suspect (though haven't proven at the storage layer) that this same failure, hitting mid-write of the session state, is why the persisted conversation history ends up corrupted/duplicated on the next load — see the duplicate tool_use IDs and inconsistent JSON casing described above.

Image

Steps to reproduce

  • Open a Copilot Workspace chat conversation against any content-heavy site.
  • Ask a question that triggers several tool calls and a reasonably long streamed response (e.g. "give me a critique of this article").
  • Continue the conversation with at least one or two follow-up messages in the same thread.
  • Observe the response cut off mid-sentence with no error shown in the UI; check the app's server logs for the InvalidOperationException above around the same timestamp.

Expected result / actual result

No response

Dependencies

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions