Repository navigation
.NET: runtime logs forwarding session.idle, but SDK callback never delivers it #2008
Description
Summary
Visual Studio can remain indefinitely in the running state after the Copilot CLI has completed a response.
The CLI verbose log shows that it forwarded the terminal, ephemeral session.idle event. The .NET SDK subscription receives the preceding events, including the final assistant.turn_end, but never receives SessionIdleEvent.
Because session.idle is ephemeral, it cannot be recovered later through GetEventsAsync(). The client therefore has no authoritative completion signal and continues waiting.
Environment
- GitHub.Copilot.SDK:
1.0.1 - Copilot CLI runtime:
1.0.61 - Windows ARM64
- Visual Studio Agent and Agent (Preview) modes
The current SDK 1.0.7 still does not expose a durable idle wait or event-dispatch flush API.
Observed behavior
The CLI log ends with:
Forwarding event ... assistant.turn_end
Forwarding event ... session.idle (ephemeral)
The .NET subscriber observes assistant.turn_end, but not session.idle. The operation remains active even though the runtime has finished.
The absence of session.idle from events.jsonl is expected because the event is ephemeral; it does not explain why the live subscriber missed it.
Why assistant.turn_end is insufficient
In multi-turn tool workflows, an intermediate assistant.turn_end can be followed immediately by another assistant.turn_start. Completion also needs to account for sub-agents, attached shells, queued messages, and follow-up notification turns.
Requested fix
Please provide an authoritative completion contract owned by the runtime/SDK, for example:
await session.WaitForIdleAsync(cancellationToken);It should:
- Wait for turns, sub-agents, attached shells, queued work, and follow-up turns to settle.
- Resolve only when the runtime is fully idle.
- Ensure preceding session events have crossed the .NET SDK FIFO dispatch queue.
- Surface session errors and transport failures.
- Remain correct if the live
session.idlenotification is dropped. - Support cancellation without leaving an orphaned waiter.
An equivalent session.waitForIdle runtime RPC combined with an SDK dispatch barrier would also work.
Regression test
Add a .NET test where a multi-turn response completes, the live session.idle notification is deliberately dropped, and the completion wait still resolves only after all preceding assistant and tool events have been delivered.
Related
- CLI:
processQueuedItems()lacks error handling —session.idlemay never be emitted #794 - identifies missingsession.idleand silent notification drops, but its proposed fallback did not ship - TimeoutError: Timeout after XXXs waiting for session.idle #558 - timeout waiting for
session.idle - Serialize event dispatch in .NET and Go SDKs #791 - serialized FIFO event dispatch in the .NET SDK
- Update runtime to 1.0.12-0 #927 - ephemeral events are intentionally unavailable through replay
sendAndWaitreturns prematurely when orchestrator dispatches sub-agents viataskand ends its turn #1275 -assistant.turn_endalone is not a completion signal
Activity
github-actions commentedon Jul 16, 2026
Investigation findings
Label applied:
bugI investigated the reported behavior by examining the following files:
dotnet/src/Session.cs— event channel andSendAndWaitAsyncimplementationdotnet/src/Client.cs—OnSessionEvent/OnSessionLifecycleRPC handlers and method registrationsdotnet/src/JsonRpc.cs— JSON-RPC dispatchdotnet/src/Generated/SessionEvents.cs—SessionIdleEventdeserializationWhat I found
The SDK architecture is designed to deliver
session.idlereliably to live subscribers:session.idleis properly registered as a[JsonDerivedType]and will be deserialized toSessionIdleEvent.OnSessionEventinClient.cscallssession.DispatchEvent(evt), whichTryWrites it into an unboundedChannel<SessionEvent>and thenProcessEventsAsyncdelivers it serially to all registered handlers — no filtering or intentional dropping occurs in the SDK.SendAndWaitAsyncregisters itsSessionIdleEventhandler before callingSendAsync, so there is no subscription race window on the SDK side.session.idleevent — yet the SDK subscriber misses it in practice on Windows ARM64.Root cause assessment
This is a genuine bug. The SDK's completion contract (
SendAndWaitAsync) has no fallback when the livesession.idlenotification is dropped in transit. Because the event is ephemeral it cannot be recovered viaGetEventsAsync(), leaving the caller waiting indefinitely (until the 60 s timeout fires).Previous issue #794 identified this drop and proposed a fallback that was never shipped. Issue #558 tracks the resulting timeout. The combination confirms this is a known but unresolved transport reliability gap, not intentional design.
What a fix should address
WaitForIdleAsync-style RPC call that is answered by the runtime rather than relying solely on an ephemeral push notification.session.idlenotification and verifies the completion wait still resolves (e.g., via a polled RPC fallback) would prevent recurrence.I have applied the
buglabel accordingly.Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
awmgmcpgSee Network Configuration for more information.
SteveSandersonMS commentedon Jul 28, 2026
Thanks for reporting this, and especially for including the CLI-side event sequence. We want to ensure Visual Studio has a reliable completion contract.
One requested requirement is:
The transport is intended to provide reliable, ordered event delivery. We generally cannot support individual events being unpredictably dropped: losing assistant, tool, permission, or error events could break much more than idle detection. We are not currently aware of a confirmed case where one event disappears while the connection remains healthy. If that can be reproduced, we would prioritize investigating and fixing the delivery defect itself.
There is a separate consideration around resume:
session.idleis ephemeral and is not persisted or replayed. A completed session resumed later will not receive another copy of its previous idle event. An active resumed session should receive a future idle event when its work finishes.Could you share a small code sample or pseudocode showing:
SendAndWaitAsyncor an event subscription; andFor example:
If the same live subscription receives
assistant.turn_endfor newly sent work but not the followingsession.idle, that would be particularly important. A minimal console reproduction would be ideal, but stripped-down pseudocode from the Visual Studio flow is enough to begin; no wire-level trace is needed at this stage.