Skip to content

agents: fix spinner stuck after session completes#314608

Merged
roblourens merged 2 commits intomainfrom
roblou/agents/fix-session-spinner-issue
May 6, 2026
Merged

agents: fix spinner stuck after session completes#314608
roblourens merged 2 commits intomainfrom
roblou/agents/fix-session-spinner-issue

Conversation

@roblourens
Copy link
Copy Markdown
Member

Problem

Completed agent host sessions show a perpetual spinner in the Sessions list. The session finishes successfully but the UI never transitions away from the "in progress" state.

Root cause

There is a race between AgentHostStateManager's _summaryNotifyScheduler (100 ms debounce) and the call to removeSession triggered by _maybeEvictIdleSession.

  1. SessionTurnComplete is processed → endTurn produces a new summary with status = Idle → session is added to _dirtySummaries_summaryNotifyScheduler.schedule() (fires in 100 ms).
  2. Within that 100 ms window: the client calls unsubscribe, _maybeEvictIdleSession sees activeTurn === undefined and no remaining subscribers → calls removeSession.
  3. removeSession clears _sessionStates, _lastNotifiedSummaries, and _dirtySummaries.
  4. The scheduler fires 100 ms later, finds nothing dirty → no SessionSummaryChanged notification is ever emitted.
  5. The client's _handleSessionSummaryChanged never receives status = Idle → spinner stays.

Evidence from live IPC/agent-host logs: SessionTurnComplete envelopes arrive for all three sessions, followed ~250 ms later by "Action for unknown session" warnings — confirming the session was evicted before the flush.

Fix

removeSession: before clearing state maps, synchronously flush any pending summary notification. This guarantees clients see the final status even when a session is evicted within the debounce window.

deleteSession (permanent removal): drop the session from _dirtySummaries before calling removeSession. The forthcoming SessionRemoved notification supersedes any pending summary diff, so we preserve the existing invariant that permanently-deleted sessions don't emit spurious SessionSummaryChanged events.

Testing

Added a regression test to agentHostStateManager.test.ts:

  • Start a turn → let the scheduler fire so _lastNotifiedSummaries records status = InProgress
  • Complete the turn → call removeSession before the 100 ms debounce fires
  • Assert SessionSummaryChanged { status: Idle } was emitted synchronously inside removeSession

All 31 existing tests continue to pass.

(Written by Copilot)

Race condition: _summaryNotifyScheduler debounces SessionSummaryChanged
notifications by 100ms. When _maybeEvictIdleSession calls removeSession
within that window after a turn completes, the session is removed from
_dirtySummaries before the scheduler fires. Clients never receive the
status=Idle notification and the spinner stays stuck.

Fix: in removeSession, flush any dirty summary synchronously before
clearing the session maps. This guarantees clients see the final status
even when the session is evicted within the debounce window.

For deleteSession (permanent removal), the forthcoming SessionRemoved
notification supersedes any summary diff, so dirty summaries are
dropped before removeSession is called to preserve the existing
"no spurious SessionSummaryChanged on delete" invariant.

 scheduler fired (status=InProgress

assert SessionSummaryChanged{status=Idle} emitted synchronously.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 6, 2026 01:19
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Screenshot Changes

Base: 724775b9 Current: d7dafffe

Changed (6)

chat/input/chatInput/WithArtifacts/Light
Before After
before after
chat/input/chatInput/WithFileChanges/Dark
Before After
before after
chat/input/chatInput/WithFileChanges/Light
Before After
before after
chat/input/chatInput/WithTodosAndFileChanges/Dark
Before After
before after
chat/input/chatInput/WithArtifactsAndFileChanges/Light
Before After
before after
chat/widget/chatWidget/MultiTurn/Light
Before After
before after

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 a race in the agent-host sessions process where a session can be evicted during the debounced summary-notification window, causing clients to miss the final status = Idle update and leaving the Sessions list spinner stuck “in progress”.

Changes:

  • Flushes any pending SessionSummaryChanged notification synchronously during removeSession teardown, ensuring the final status reaches clients even if eviction happens before the debounce fires.
  • Ensures deleteSession suppresses any pending summary diff so clients don’t see a last-moment SessionSummaryChanged right before SessionRemoved.
  • Adds a regression test covering eviction within the debounce window after a turn completes.
Show a summary per file
File Description
src/vs/platform/agentHost/node/agentHostStateManager.ts Flush pending summary notifications on eviction; refactors flush logic into a helper; suppresses pending diffs on delete.
src/vs/platform/agentHost/test/node/agentHostStateManager.test.ts Adds a regression test verifying removeSession synchronously emits the final status = Idle summary change.

Copilot's findings

Comments suppressed due to low confidence (1)

src/vs/platform/agentHost/node/agentHostStateManager.ts:417

  • For consistency with the rest of AgentHostStateManager (which uses the protocol URI type alias for session identifiers), consider changing _flushSummaryNotificationFor(session: string) to _flushSummaryNotificationFor(session: URI). This avoids mixing raw string with the semantic URI type in this API surface.
	/**
	 * Emits a {@link NotificationType.SessionSummaryChanged} notification for
	 * `session` if its current summary differs from the last one sent to
	 * clients, then advances `_lastNotifiedSummaries` to the current summary.
	 *
	 * Does NOT remove `session` from `_dirtySummaries` — callers are
	 * responsible for that bookkeeping.
	 */
	private _flushSummaryNotificationFor(session: string): void {
		const state = this._sessionStates.get(session);
  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/vs/platform/agentHost/node/agentHostStateManager.ts Outdated
Comment thread src/vs/platform/agentHost/node/agentHostStateManager.ts Outdated
Per Copilot review on #314608:
- JSDoc now clarifies removeSession won't emit SessionRemoved but may
  emit SessionSummaryChanged (synchronous flush side-effect)
- Remove 'as unknown as string' cast on _dirtySummaries. thedelete
  other delete calls on the same maps use session directly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roblourens roblourens marked this pull request as ready for review May 6, 2026 01:51
@roblourens roblourens enabled auto-merge (squash) May 6, 2026 01:51
@roblourens roblourens merged commit 50ab69d into main May 6, 2026
40 of 41 checks passed
@roblourens roblourens deleted the roblou/agents/fix-session-spinner-issue branch May 6, 2026 04:36
@vs-code-engineering vs-code-engineering Bot added this to the 1.120.0 milestone May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants