Skip to content

Commit d8978f3

Browse files
PureWeenCopilot
andcommitted
Fix: Blazor render crash on session delete
Session deletion triggered rapid render batch churn: 1. Session.DisposeAsync() talked to CLI, triggering SDK events 2. OnStateChanged fired from CloseSessionAsync 3. ReconcileOrganization fired another OnStateChanged This caused Blazor render batch ordering races ('r.parentNode.removeChild' on null) and 'unexpected acknowledgement for render batch N' errors. Fix: move DisposeAsync to fire-and-forget after UI update, and remove redundant ReconcileOrganization call (session already removed from _sessions, reconciliation just caused extra state churn). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c52948b commit d8978f3

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

PolyPilot/Services/CopilotService.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,9 +2411,6 @@ internal async Task<bool> CloseSessionCoreAsync(string name, bool notifyUi)
24112411
// Track session close using display name (consistent with TrackSessionStart key)
24122412
_usageStats?.TrackSessionEnd(name);
24132413

2414-
if (state.Session is not null)
2415-
try { await state.Session.DisposeAsync(); } catch { /* session may already be disposed */ }
2416-
24172414
// Clean up auto-created temp directory for empty sessions
24182415
if (state.Info.WorkingDirectory != null)
24192416
{
@@ -2433,12 +2430,26 @@ internal async Task<bool> CloseSessionCoreAsync(string name, bool notifyUi)
24332430
_activeSessionName = _sessions.Keys.FirstOrDefault();
24342431
}
24352432

2433+
// Single state change notification — ReconcileOrganization is unnecessary here
2434+
// because the session was already removed from _sessions above. Calling it would
2435+
// trigger a second OnStateChanged, causing rapid render batch churn that crashes
2436+
// Blazor with "r.parentNode.removeChild" on null (render batch ordering race).
24362437
if (notifyUi)
24372438
OnStateChanged?.Invoke();
24382439
if (!IsRemoteMode)
2439-
{
24402440
SaveActiveSessionsToDisk();
2441-
ReconcileOrganization();
2441+
2442+
// Dispose the SDK session AFTER UI has updated — DisposeAsync talks to the CLI
2443+
// process and may trigger additional SDK events on background threads. Running it
2444+
// after the state change prevents render batch collisions.
2445+
if (state.Session is not null)
2446+
{
2447+
var session = state.Session;
2448+
_ = Task.Run(async () =>
2449+
{
2450+
try { await session.DisposeAsync(); }
2451+
catch { /* session may already be disposed */ }
2452+
});
24422453
}
24432454
return true;
24442455
}

0 commit comments

Comments
 (0)