Skip to content

Close the dashboard debug browser when the Aspire session ends - #19296

Merged
Adam Ratzman (adamint) merged 4 commits into
microsoft:mainfrom
adamint:adamint/fix-dashboard-debug-browser-close
Aug 12, 2026
Merged

Adam Ratzman (adamint) merged 4 commits into
microsoft:mainfrom
adamint:adamint/fix-dashboard-debug-browser-close

Conversation

@adamint

@adamint Adam Ratzman (adamint) commented Aug 12, 2026

Copy link
Copy Markdown
Member

Description

When you stop an Aspire debug session in VS Code, the dashboard browser window stays open. You end up collecting orphaned Edge/Chrome windows pointing at dashboards for AppHosts that are long gone.

AspireDebugSession already had a closeDashboard() that implements the aspire.closeDashboardOnDebugEnd setting (default true), but nothing ever called it — it was dead code. VS Code does not stop a child browser debug session when its parent session terminates, so the browser just outlived the session. This calls it from dispose().

Reviewing that one-line call surfaced two more holes in the same path that the call site turns into user-visible behavior:

  • The started-session listener matched only on configuration name and browser type. Every Aspire session launches its dashboard with the same name and the same user-configured browser type, so with two AppHosts running, session A could adopt session B's browser, close B's window on stop, and leave its own orphaned. It now matches on the parent session id.
  • The Aspire session can be disposed while the browser is still launching — an AppHost that exits right after reporting the dashboard URL does exactly this. The late-arriving session was stored on an already-disposed instance and never stopped, and a failed launch fell back to openExternal after teardown, popping a window open during shutdown. Both paths re-check disposal now, and the first routes back through closeDashboard() so the opt-out setting is still honored.

Also fixes two doc comments that still claimed VS Code auto-closes the dashboard child session, since that assumption is what caused this.

Note this is a different path from #18626, which handles the DCP WithBrowserDebugger() browser lifecycle. This one is the dashboard browser launched by openDashboard.

Fixes #19289

Verification

Five unit tests, all written first and observed failing (extension/src/test/aspireDebugSession.test.ts).

Beyond that, I ran an A/B against a real VS Code Extension Development Host, a real Aspire AppHost, and the real Edge instance js-debug launches. Playwright CLI confirms the dashboard actually rendered before the stop; the Edge pid is then polled for 30s after the stop:

Build closeDashboardOnDebugEnd Edge after stop Result
with fix true exited (~1s) closes, as expected
fix reverted true still running after 30s reproduces #19289
with fix false still running opt-out honored

The only difference between rows 1 and 2 is the closeDashboard() call. Worth noting debugSessions is empty in all three runs — the extension always believed the session had ended, which is why this was invisible from extension state and only observable at the process level.

There is also a new E2E test, closes the dashboard debug browser when the AppHost debug session stops in extension/src/test-e2e/debugDashboard.e2e.test.ts. It configures aspire.dashboardBrowser: debugChrome, starts the AppHost, asserts the dashboard browser session is parented to the Aspire session, stops debugging, and requires the browser session to terminate.

That test needed a new observable. The extension's own debugSessions was empty in every repro run, so asserting on extension state cannot catch this regression; the divergence only exists in VS Code's debug sessions, which the state snapshot does not expose. The test-only E2E state file bridge now tracks pwa-chrome/pwa-msedge/firefox sessions as browserDebugSessions. Re-running the live A/B against that field: empty after stop with the fix, and with the fix reverted both the root Aspire Dashboard session and its page child are still present, so the new assertion fails without the fix.

One aside for anyone trying to reproduce this with Playwright directly: js-debug launches Edge with --remote-debugging-pipe, not --remote-debugging-port, so there's no HTTP CDP endpoint and no DevToolsActivePort file to attach to. You have to track the process instead.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Adam Ratzman and others added 2 commits August 12, 2026 14:21
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37d5f71d-3c66-4bae-909d-5f710316b2f2
Review of the dispose fix surfaced two lifecycle holes that the new call
site turns into user-visible behavior.

The started-session predicate matched only on configuration name and
browser type. Concurrent Aspire debug sessions all launch their dashboard
with the same name and the same user-configured browser type, so one
session could adopt another session's browser, later close it, and leave
its own browser orphaned. Match on the parent session id instead.

The Aspire session can also be disposed while the browser is still
launching, for example when the AppHost exits right after reporting the
dashboard URL. The late-arriving session was stored on an already-disposed
instance and never stopped, and a failed launch fell back to an external
browser after teardown. Both paths now re-check disposal, routing through
closeDashboard so the opt-out setting is still honored.

Also corrects two doc comments that still claimed VS Code auto-closes the
dashboard child session, which is the assumption this fix disproves.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37d5f71d-3c66-4bae-909d-5f710316b2f2
Copilot AI balanced review requested due to automatic review settings August 12, 2026 18:23
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19296

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19296"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensures dashboard debug browsers are cleaned up when Aspire sessions end.

Changes:

  • Stops tracked dashboard browser sessions during disposal.
  • Associates browser sessions with the correct parent and handles launch/disposal races.
  • Adds five focused lifecycle unit tests.
Show a summary per file
File Description
extension/src/debugger/AspireDebugSession.ts Implements dashboard browser lifecycle cleanup.
extension/src/test/aspireDebugSession.test.ts Adds regression tests for cleanup, opt-out, ownership, and races.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread extension/src/debugger/AspireDebugSession.ts
The unit tests cover the extension's own bookkeeping, but the regression in
microsoft#19289 lived below that: the
extension's debugSessions list was already empty while the launched browser
kept running. An E2E test needs to observe VS Code's browser debug sessions
directly, which the extension state snapshot does not expose.

Track browser debug sessions (pwa-chrome, pwa-msedge, firefox) in the
test-only E2E state file bridge, then add a debug-dashboard E2E test that
configures the debug browser, starts the AppHost, asserts the dashboard
browser session is parented to the Aspire session, stops debugging, and
requires the browser session to terminate.

Verified against a real Extension Development Host: with the fix the tracked
browser sessions are empty after stopping, and with the fix reverted both the
root browser session and its page child are still present, so the new
assertion reproduces the reported bug.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37d5f71d-3c66-4bae-909d-5f710316b2f2
Copilot AI review requested due to automatic review settings August 12, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit dashboard-session cleanup is correctly scoped to its owning Aspire session and handles disposal during browser launch. The focused unit coverage plus the new VS Code E2E regression test cover the reported lifecycle failure and the relevant races.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

The Linux E2E runner cannot launch Chrome under xvfb. js-debug activates on
`onDebugResolve:pwa-chrome` and then never resolves `startDebugging`, so the
`openDashboard` await never returns, the AppHost startup handshake never
completes, and the stall cascades into the rest of the file. The Windows
runner launches the real browser and the test passed there, so the shutdown
behavior still has end-to-end coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37d5f71d-3c66-4bae-909d-5f710316b2f2
Copilot AI review requested due to automatic review settings August 12, 2026 19:37
@adamint

Copy link
Copy Markdown
Member Author

Follow-up on the E2E test, since the first CI run answered a question I could only guess at locally.

Windows passed the new test. Linux could not run it, so it is now gated to win32.

On the Linux xvfb runner, js-debug activates on onDebugResolve:pwa-chrome and then never resolves startDebugging — it cannot launch Chrome there. openDashboard awaits that launch, so the displayDashboardUrls RPC never returned:

19:20:10.054 Displaying dashboard URLs.
19:20:10.054 Dashboard: https://localhost:34709
19:20:10.054 Opening dashboard in browser: debugChrome.
19:20:10.092 ExtensionService#_doActivateExtension ms-vscode.js-debug, activationEvent: 'onDebugResolve:pwa-chrome'
... nothing for 4 minutes ...
19:24:08.153 [error] Interaction service endpoint 'displayDashboardUrls' failed: Canceled

AppHost startup completed and dashboard is running. never logged, unlike the two preceding tests in the same run. The stall then cascaded into the rest of the file.

Two things worth separating out of that:

  1. The test. Windows launches the real browser and passed, so the shutdown behavior still has genuine end-to-end coverage; the Linux skip carries this explanation inline.
  2. A pre-existing product issue this surfaced. A dashboard debug browser that never finishes launching blocks AppHost startup, because openDashboard is awaited inside the displayDashboardUrls handler. A user who selects debugChrome/debugEdge/debugFirefox without that browser installed would sit on "Starting dashboard..." indefinitely. That is independent of this fix and predates it, so I have deliberately left it out of this PR rather than widen the scope of an already-reviewed change. Happy to open a separate issue for it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@adamint
Adam Ratzman (adamint) enabled auto-merge (squash) August 12, 2026 19:44
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@adamint
Adam Ratzman (adamint) merged commit bb1d405 into microsoft:main Aug 12, 2026
718 of 722 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.6 milestone Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Extension] Dashboard debug browser remains open when closeDashboardOnDebugEnd is enabled

3 participants