Skip to content

Stop the Aspire CLI and own its RPC connections during extension deactivation - #19152

Draft
Adam Ratzman (adamint) wants to merge 13 commits into
microsoft:mainfrom
adamint:adamint/extension-shutdown-lifecycle
Draft

Stop the Aspire CLI and own its RPC connections during extension deactivation#19152
Adam Ratzman (adamint) wants to merge 13 commits into
microsoft:mainfrom
adamint:adamint/extension-shutdown-lifecycle

Conversation

@adamint

Copy link
Copy Markdown
Member

Description

Split out of #19124, which combined extension shutdown/lifecycle hardening with an unrelated change to how activity progress is displayed. Separating them reduced #19124 from 24 files / +2288/-120 to 11 files / +280/-37. There is no dependency between the two branches; they touch disjoint hunks and can merge in either order.

Two independent changes, one per commit.

4f34b592 — Stop the Aspire CLI and own RPC connections during deactivation

Extension deactivation was fire-and-forget. deactivate() returned void, so VS Code did not wait for the CLI stop requests it triggered, and closing a window could leave aspire run processes alive. Connections whose debug-session handshake was still pending were not tracked by the RPC server, so they were never disposed. InteractionService had no disposal at all, so a status message still in flight when the transport closed could paint progress that nothing remained alive to clear.

  • extension.tsdeactivate() returns a promise and awaits AspireExtensionContext.deactivate().
  • AspireExtensionContext.ts (+102/-2) — asks every live debug session to stop its CLI, then disposes the rest of the extension. The wait is bounded at 5s so a hung CLI cannot block window close.
  • AspireDebugSession.ts (+12) — requestCliStopForExtensionShutdown() deduplicates concurrent requests by reusing the in-flight promise.
  • AspireRpcServer.ts (+82/-8) — tracks connections it creates, including those still inside the handshake, and disposes them on server disposal. Connections added after disposal are rejected and disposed rather than published.
  • rpcClient.ts (+26/-3) — dispose() is idempotent and closes the transport.
  • interactionService.ts (+18/-2) — implements vscode.Disposable and latches disposal, so a late showStatus after the connection closed is ignored.

a0cb46e7 — Reject foreign absolute paths in getRelativePathToWorkspace

vscode.workspace.asRelativePath returns its input unchanged when the path cannot be made relative, and that value was returned verbatim. It resolves against the workspace, which does not necessarily share the extension host's path semantics. A Windows absolute path such as C:\Users\... or \\server\share\... therefore satisfies the host's path.isAbsolute on POSIX, which is the case for remote SSH, WSL and Codespaces, and the full path was used as the debug configuration name.

  • utils/workspace.ts (+23/-9) — rejects both path.posix.isAbsolute and path.win32.isAbsolute before accepting the relative path, falling back to the workspace folder name. When the target is outside every workspace folder, the file name is used instead of the full path.

The single caller is the debug configuration name built in interactionService.startDebugSession.

Tests

  • AspireExtensionContext.test.ts (+216, new) — deactivation stops the CLI for every session, tolerates rejection and timeout, and disposes in order.
  • rpc/aspireRpcServer.test.ts (+199, new) — server disposal while a handshake is pending, rejected handshake on an open transport, transport disposal mid-handshake, and connections added after disposal.
  • rpc/interactionServiceTests.test.ts (+43/-1) — client disposal closes the transport once and prevents late status resurrection; server disposal clears status when the connection never closes.
  • aspireDebugSession.test.ts (+23) — extension shutdown reuses an in-flight CLI stop request.
  • workspace.test.ts (+59/-1) — C:\Users\..., \\server\share\... and /home/... all fall back to the workspace name regardless of host platform; relative paths in either separator style are preserved; a path outside the workspace resolves to its file name.

Extension suite on this branch: 1468 passing, 4 pending, exit 0 (yarn run compile-tests, yarn run lint, yarn run unit-test).

Base

Based on e79efb125c rather than current main, because main does not currently compile: #19084 and #18976 merged 27 seconds apart and combine into error CS0117: 'AzureBicepResourceScope' does not contain a definition for 'ForSubscription'. #19148 fixes that. Any Hosting.Azure or Hosting.Azure.Kubernetes failure on this PR originates there, not from this branch, which changes only extension/src/**.

Fixes # (no linked issue; split out of #19124)

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

Copilot AI balanced review requested due to automatic review settings August 7, 2026 19:26
@github-actions

github-actions Bot commented Aug 7, 2026

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 -- 19152

Or

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

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

Hardens VS Code extension shutdown and prevents absolute paths from appearing in debug configuration names.

Changes:

  • Awaits CLI stop requests during deactivation with bounded teardown.
  • Owns and disposes RPC connections and status notifications.
  • Adds cross-platform workspace-path handling and regression tests.
Show a summary per file
File Description
extension/src/extension.ts Awaits extension deactivation.
extension/src/AspireExtensionContext.ts Coordinates bounded CLI shutdown and disposal.
extension/src/debugger/AspireDebugSession.ts Deduplicates CLI stop requests.
extension/src/server/AspireRpcServer.ts Tracks and disposes RPC clients.
extension/src/server/rpcClient.ts Adds idempotent transport disposal.
extension/src/server/interactionService.ts Clears and suppresses disposed status updates.
extension/src/utils/workspace.ts Adds cross-platform path filtering.
extension/src/test/AspireExtensionContext.test.ts Tests deactivation sequencing and failures.
extension/src/test/aspireDebugSession.test.ts Tests stop-request deduplication.
extension/src/test/rpc/aspireRpcServer.test.ts Tests RPC ownership during races.
extension/src/test/rpc/interactionServiceTests.test.ts Tests transport and status cleanup.
extension/src/test/workspace.test.ts Tests path fallback behavior.

Review details

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

Comment thread extension/src/utils/workspace.ts
Adam Ratzman and others added 3 commits August 7, 2026 15:44
Extension deactivation was fire-and-forget: `deactivate()` returned void, so
VS Code never waited for the CLI stop requests it triggered, and connections
whose debug-session handshake was still pending were never owned by the RPC
server. A window close could therefore leave `aspire run` processes alive and
leave progress indicators on screen with nothing left to clear them.

- `deactivate()` now returns a promise and awaits `AspireExtensionContext.deactivate()`,
  which asks every live debug session to stop its CLI (deduplicating in-flight
  requests) with a bounded 5s timeout before disposing the rest of the extension.
- `AspireRpcServer` tracks the connections it creates, including ones still
  inside the handshake, and disposes them on server disposal.
- `RpcClient.dispose()` is idempotent and closes the transport.
- `InteractionService` is disposable and latches disposal so a status message
  still in flight when the transport closed cannot resurrect progress.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`asRelativePath` returns its input unchanged when the path cannot be made
relative, and that value was returned verbatim. It resolves against the
workspace, which may not share the extension host's path semantics, so a
Windows absolute path (`C:\Users\...` or `\\server\share\...`) passes the
host's `path.isAbsolute` on POSIX — the case for remote SSH, WSL and
Codespaces — and the full path leaked into the debug configuration name.

Reject both POSIX and Win32 absolute forms and fall back to the workspace
folder name, and use the file name rather than the full path when the target
is outside every workspace folder.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The cross-platform absolute-path rejection sat after the
getWorkspaceFolder early return, so it was unreachable for the input it
existed to catch. A Windows path is inside no workspace folder on a POSIX
host, so getWorkspaceFolder returns undefined and control reached
path.basename, which on POSIX does not split on '\' and returned
C:\Users\me\secret\AppHost.csproj whole as the debug configuration name.

Move the rejection ahead of that early return and reduce the path with
path.win32.basename, which splits on both separators. Only Win32 forms can
be foreign, because path.win32.isAbsolute also accepts a leading '/'.

The existing regression test stubbed getWorkspaceFolder to return a folder
for the Windows paths, which cannot happen on a POSIX host, so it was green
against a code path that never ran. It now covers the asRelativePath guard
with a host-native path, and a new test drives the foreign paths with
getWorkspaceFolder returning undefined, asserting both the file name and
that no separator survives on either host platform.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 7, 2026 19:49
@adamint
Adam Ratzman (adamint) force-pushed the adamint/extension-shutdown-lifecycle branch from a0cb46e to f7e616a Compare August 7, 2026 19:49

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: 12/12 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread extension/src/AspireExtensionContext.ts
Comment thread extension/src/AspireExtensionContext.ts Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

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.

… not

`stopCli` is an RPC request, not a kill. It resolves without effect when the
transport is already closed and never settles when the CLI has stopped
servicing the connection, so neither outcome proves the process exited.
`spawnAspireCommand` discarded the ChildProcess that `spawnCliProcess`
returns and did not request a process group, so there was nothing to signal
as a fallback. Every other CLI spawn site in the extension already retains
its child and calls `terminateCliProcess`; the longest-lived one did not.

Retain the child, spawn `aspire run` as a process-group leader, and add
`terminateCliProcessTree()`. Session disposal escalates to it after a 10s
grace period so a cooperative stop still gets the first chance to shut
resources down cleanly, and deactivation calls it directly once the stop
requests settle or time out.

Also re-snapshot the session array between awaits during deactivation.
`_isShuttingDown` does not gate `addAspireDebugSession`, so a debug-adapter
descriptor or an RPC-triggered `startDebugSession` landing mid-await was
never asked to stop. Requesting a stop is idempotent per session, so
re-scanning until no new session appears is safe.

Verified red-green: with the escalation, the process group and the
re-snapshot loop reverted, 4 of the 5 new tests fail and the existing 5
deactivation tests still pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 7, 2026 20:47

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: 12/12 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread extension/src/debugger/AspireDebugSession.ts Outdated
Comment thread extension/src/AspireExtensionContext.ts Outdated
The re-snapshot loop asks every session that appears *before* teardown to
stop, but `_disposeCore` disposes exactly the sessions present when it takes
its snapshot and never runs again. A session registered after that point was
still tracked forever and never disposed, so its CLI kept running with
nothing left alive to stop it. `addAspireDebugSession` now refuses and
disposes once `_isDisposed` is set; the pre-teardown window is unchanged and
still handled by the drain.

`spawnAspireCommand` awaits the CLI path before spawning, so deactivation can
complete inside that await. Spawning afterwards produced an `aspire run` that
no teardown path could reach — and now that it is spawned detached as a
process-group leader, one that would not even die with the extension host.

Two fixes to the tests added alongside the process-group change:

- `terminateCliProcessTree signals a running CLI process` ran the real
  `terminateCliProcess`, which on Windows shells out to `taskkill /pid <pid>
  /t` rather than calling `child.kill`. The assertion would have failed on
  the Windows CI agents, and the run would have signalled whatever process
  owned PID 4322 there. It now stubs the module function.
- Restore the newline that was lost from the `reuses an in-flight CLI stop
  request` test declaration.

Also drops the `if (deactivate)` fallback in the test helper. `deactivate`
is a declared method, so the fallback could never run, and had it ever run
it would have silently retargeted the suite at `dispose()`.

Verified red-green: reverting the two guards fails exactly the two new tests
and nothing else. 1476 passing, 0 failing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 7, 2026 20:59

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

Suppressed comments (1)

extension/src/AspireExtensionContext.ts:171

  • A session can dispose/remove itself while _settleStopRequests is awaiting (for example when its AppHost session terminates). This final loop then loses that session's process handle, so a hung stop receives no immediate termination; only the session's unref'd 10-second timer remains, even though deactivation resolves after 5 seconds and the extension host may exit first. Retain the session objects associated with every collected stop request and terminate the union of those sessions and the currently registered sessions. A regression test should remove a session while its stop request is pending.
        for (const session of this._aspireDebugSessions) {
            try {
                session.terminateCliProcessTree();
  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

…tion

An Aspire CLI spawned with createProcessGroup leads a detached group that the
AppHost and every resource process joins. Two paths let that group outlive the
extension:

- When the CLI exited on its own, the exit callback only cancelled the escalation
  timer and terminateCliProcessTree early-returned on an exited leader, so nothing
  ever signalled the surviving descendants. terminateCliProcess already reaps a
  managed group whose leader has exited; it just was not being invoked. Collect
  synchronously from the exit callback, because once the leader's PID is released
  the OS may recycle it as another group's id.

- The deactivation sweep sent SIGTERM and scheduled the hard kill on an unref'd
  timer, but _deactivateCore resolves as soon as the sweep returns, so the host
  could exit first and leave a CLI that ignored SIGTERM alive. Deactivation has
  already spent its 5s cooperative window, so it now forces immediately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 7, 2026 21:15

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: 14/14 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread extension/src/AspireExtensionContext.ts
Comment thread extension/src/debugger/AspireDebugSession.ts
@github-actions

github-actions Bot commented Aug 7, 2026

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.

`terminateCliProcess` never calls `child.kill` on Windows: it spawns
`taskkill.exe /pid <pid> /t` so the descendants come down with the leader, and
only falls back to `child.kill` from taskkill's error handler. That branch had no
coverage anywhere, which is how a test asserting `child.kill` reached CI — it
passed on macOS and Linux and could only fail on the Windows unit-test job, the
one leg with no counterpart on another platform.

Assert the taskkill invocation and that the child is not signalled directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 7, 2026 21:51

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: 14/14 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread extension/src/debugger/languages/cli.ts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 8, 2026 17:31

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

Suppressed comments (1)

extension/src/AspireExtensionContext.ts:164

  • The re-scan only runs after _settleStopRequests returns. If an existing stop request hangs, a session registered during that five-second wait is never passed to requestCliStopForExtensionShutdown() because this branch breaks immediately at the deadline; it is then force-killed without any cooperative cleanup attempt. Wake the wait when addAspireDebugSession adds a session (or otherwise re-scan while waiting), and cover a hung first session plus a responsive late session.
        while (this._collectStopRequests(requested) && Date.now() < deadline) {
            const timedOut = await this._settleStopRequests([...requested.values()], deadline);
            if (timedOut) {
                extensionLogOutputChannel.warn(`Timed out after ${AspireExtensionContext._cliStopTimeoutMs}ms waiting for Aspire CLI stop requests; continuing extension teardown.`);
                break;
  • Files reviewed: 15/15 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread extension/src/debugger/AspireDebugSession.ts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 8, 2026 17:55

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

Suppressed comments (1)

extension/src/debugger/languages/cli.ts:176

  • taskkill /pid <pid> /t targets the specified live process and its children; once the CLI leader has exited, that PID no longer identifies its former tree (and may be reused). Therefore this branch cannot reliably terminate the orphaned AppHost/resource processes and can potentially target an unrelated process after PID reuse. The new exited-leader unit test only verifies that taskkill is spawned, not that it can find the old tree. Windows needs ownership retained while the leader is alive (for example, a Job Object) or descendant PIDs captured before exit rather than post-exit cleanup by the stale PID.
        // Windows does not tie child lifetimes to the parent process. An exited CLI leader can
        // still have an AppHost/resource tree underneath its recorded PID, so sweep it with
        // taskkill during forceful shutdown instead of treating the leader's exit as proof that
        // teardown completed. Non-force callers keep the historical no-op behavior because a short
        // helper CLI can legitimately exit before its close handler observes it.
  • Files reviewed: 15/15 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 8, 2026 18:05

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

Suppressed comments (3)

extension/src/AspireExtensionContext.ts:79

  • A session can still arrive after _waitForCliStopRequests() observes no new sessions: the await in _deactivateCore() yields once before _disposeCore(). Such a session is accepted here, missed by the force sweep, then spliced out before session.dispose() schedules its delayed termination, so window close can again leave its CLI alive. Reject/dispose sessions once _isShuttingDown is set, or make the final drain and teardown atomic with respect to registration; add coverage for registration after an empty drain.
    addAspireDebugSession(debugSession: AspireDebugSession) {
        if (this._isDisposed) {

extension/src/debugger/AspireDebugSession.ts:512

  • When the CLI exits on its own on Windows, this call is non-forced. terminateCliProcess() sees the already-exited leader and returns at cli.ts:177 without running taskkill; this method nevertheless marks _cliProcessTreeTerminationAttempted, so dispose() will not schedule another attempt and AppHost/resource descendants can remain alive. Use the forced tree sweep for this exited-leader path (and assert the option in the existing exit-callback regression test).
          this.terminateCliProcessTree();

.github/workflows/run-tests.yml:269

  • The PR description explicitly says this split contains only the extension lifecycle/path changes and “changes only extension/src/**,” but this also replaces the Azure Functions Core Tools installation for the Playground/Azure test workflow. That is an unrelated CI behavior and supply-chain change; remove it from this PR or split it into a separately described and validated change.
          core_tools_version='4.12.1'
          core_tools_directory="$RUNNER_TEMP/azure-functions-core-tools"
          core_tools_archive="$RUNNER_TEMP/Azure.Functions.Cli.linux-x64.${core_tools_version}.zip"
          curl --fail --location --retry 3 --retry-all-errors \
            --output "$core_tools_archive" \
            "https://github.com/Azure/azure-functions-core-tools/releases/download/${core_tools_version}/Azure.Functions.Cli.linux-x64.${core_tools_version}.zip"
          echo 'faf8fb8d50b5293df338bec70594b12f45730e9fe251805298859b2238cf627e  '"$core_tools_archive" | sha256sum --check -
  • Files reviewed: 16/16 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 8, 2026 18:52

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

Suppressed comments (3)

extension/src/debugger/AspireDebugSession.ts:516

  • When the CLI leader exits on Windows, this non-force call does not collect its descendants: terminateCliProcess() returns from the exited-Windows branch unless force is set. This method has already latched _cliProcessTreeTerminationAttempted, so dispose() will not schedule the force timer and then releases the session from extension-context ownership; the AppHost/resource tree can therefore survive a normal CLI exit and cannot be found during later deactivation. Force the exited-leader cleanup here.
          this.terminateCliProcessTree();

extension/src/AspireExtensionContext.ts:160

  • The condition creates a stop promise before checking whether the deadline has elapsed. If a late session is first discovered just as the previous wait reaches the deadline, the body is skipped and no allSettled handler is ever attached; a rejected stopCli() then becomes an unhandled rejection during deactivation. Check the deadline before collecting the next batch; the force-termination sweep below still handles sessions discovered after the cooperative window.
        while (this._collectStopRequests(requested) && Date.now() < deadline) {

.github/workflows/run-tests.yml:260

  • The PR description says this split contains only the extension lifecycle/path changes and “changes only extension/src/**,” but this modifies shared Playground/Azure CI provisioning. This unrelated workflow change should be removed or split/documented so the PR matches its stated scope and validation.
        shell: bash
  • Files reviewed: 16/16 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Restores .github/workflows/run-tests.yml to main for this PR, reverting the out-of-scope 05514e8 workflow change. The Azure Functions Core Tools fix now lives on adamint/fix-azfunc-core-tools-ci.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 8, 2026 19:09

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

Suppressed comments (2)

extension/src/AspireExtensionContext.ts:143

  • The force sweep runs inside an awaited helper, leaving a microtask gap before _disposeCore(). If getAspireCliExecutablePath() resolves in that gap, the sweep has already seen no _cliProcess, spawnAspireCommand() still sees _disposed === false and spawns, then _disposeCore() splices the session before its unref'd 10-second termination timer is scheduled. Deactivation can therefore resolve while that CLI survives. Run the force sweep immediately before _disposeCore() in the same synchronous continuation so a path resolution either occurs before the sweep or after the session is disposed.
            await this._waitForCliStopRequests();

extension/src/AspireExtensionContext.ts:160

  • Evaluate the deadline before collecting requests. In the current order, when the deadline expires just as a late session appears, _collectStopRequests() creates and stores its stopCli() promise, but the loop body is skipped, so no allSettled handler ever observes it. Transport disposal can then reject that promise as an unhandled rejection during shutdown.
        while (this._collectStopRequests(requested) && Date.now() < deadline) {
  • Files reviewed: 15/15 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread extension/src/debugger/AspireDebugSession.ts Outdated
@github-actions

github-actions Bot commented Aug 8, 2026

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.

@github-actions

github-actions Bot commented Aug 8, 2026

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.

@github-actions

github-actions Bot commented Aug 8, 2026

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.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 8, 2026 20:30

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

Suppressed comments (2)

extension/src/extension.ts:427

  • The core regression is only exercised with stubbed process.kill, taskkill, and RPC transports. No extension E2E test deactivates/reloads the extension while an AppHost is running, so the suite cannot detect VS Code failing to await this promise or real CLI/AppHost descendants surviving on Windows or POSIX. Add a lifecycle scenario that launches an AppHost, triggers extension-host shutdown/reload, and verifies the owned process IDs exit.
export function deactivate(): Promise<void> {
  return aspireExtensionContext.deactivate();

extension/src/debugger/AspireDebugSession.ts:541

  • This claim is inaccurate: several extension CLI spawn sites do not request a process group, including AspirePackageRestoreProvider.ts:189, e2eStateFileBridge.ts:1041, and ResourceCommandArgumentsLoader.ts:65. Keep the comment scoped to why this long-lived aspire run invocation needs process-group ownership.
        // PID when the cooperative `stopCli` RPC does not finish the job. Every other CLI spawn
        // site in the extension already does this; this one is the longest-lived of them.
  • Files reviewed: 15/15 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

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.

2 participants