Skip to content

Support live workspace AppHost updates#16836

Merged
mitchdenny merged 25 commits into
microsoft:mainfrom
adamint:fix/issue-16810-global-view
May 25, 2026
Merged

Support live workspace AppHost updates#16836
mitchdenny merged 25 commits into
microsoft:mainfrom
adamint:fix/issue-16810-global-view

Conversation

@adamint
Copy link
Copy Markdown
Member

@adamint adamint commented May 6, 2026

Description

Fixes #16810

This updates the VS Code extension and Aspire CLI AppHost discovery flow for workspaces with multiple AppHosts:

  • Keeps the VS Code tree in workspace mode when aspire ls finds multiple buildable workspace AppHosts.
  • Renders multiple running workspace AppHosts in the workspace tree, filtered to the AppHosts discovered for the current workspace.
  • Uses aspire describe --follow --apphost <path> for the selected/default workspace AppHost so workspace resources stay scoped to the intended AppHost.
  • Adds aspire ps --follow --format json --resources, emitting newline-delimited full AppHost snapshots for live running-AppHost/resource updates without increasing polling frequency.
  • Falls back to existing aspire ps polling when ps --follow or --resources is unavailable.
  • Clears extension progress notifications when the CLI JSON-RPC connection closes so stale “Scanning for running AppHosts...” notifications do not hang around.
  • Falls back to selected describe --follow resources when ps has not populated resources for the selected AppHost yet, including empty resource arrays.
  • Scopes resource actions and CodeLens reveal behavior to the owning AppHost when multiple workspace AppHosts expose duplicate resource names.

Validation:

  • ./restore.sh
  • dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-class "*.PsCommandTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • dotnet build /t:UpdateXlf src/Aspire.Cli/Aspire.Cli.csproj
  • cd extension && npm run compile-tests && ./node_modules/.bin/vscode-test --run out/test/appHostDataRepository.test.js --run out/test/appHostTreeView.test.js --run out/test/aspireCodeLensProvider.test.js && npm run lint
  • cd extension && ./node_modules/.bin/vscode-test --run out/test/rpc/interactionServiceTests.test.js
  • git diff --check
  • PR dogfood CLI install: 13.4.0-pr.16836.gf509da86
  • PR dogfood smoke: aspire ps --format json --resources returned a running AppHost with 4 resources.
  • PR dogfood smoke: aspire ps --follow --format json --resources emitted a full snapshot with 4 resources.

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
  • Does the change require an update in our Aspire docs?

Fix multi-AppHost workspace discovery so the tree defaults to global view when more than one AppHost candidate is reported, while preserving single-AppHost workspace behavior and scoping CodeLens resource state to the document's AppHost.

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

github-actions Bot commented May 6, 2026

🚀 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 -- 16836

Or

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

Keep the describe --follow compatibility messaging changes with their localization support, and classify non-unsupported AppHost exits as AppHost-version issues instead of CLI-version issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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

Updates the VS Code Aspire extension’s behavior in multi-AppHost workspaces by defaulting to global polling (instead of workspace describe --follow) when multiple AppHost candidates are discovered, and by scoping CodeLens resource state to the AppHost associated with the current document to avoid cross-AppHost state bleed.

Changes:

  • Switches to global view automatically when aspire extension get-apphosts reports multiple AppHost candidates (while preserving a selected AppHost path as a workspace fallback).
  • Enhances workspace describe --follow handling by capturing non-NDJSON output/stderr to provide more actionable compatibility errors when no JSON data is produced.
  • Updates CodeLens state resolution to only use resource state from the AppHost that corresponds to the current document; adds/extends unit tests for these scenarios.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

File Description
extension/src/views/AppHostDataRepository.ts Multi-AppHost detection and auto-switch to global view; extended describe --follow failure diagnostics and error selection logic.
extension/src/editor/AspireCodeLensProvider.ts Scopes resource CodeLens state to the AppHost matching the current document to prevent cross-AppHost resource-state borrowing.
extension/src/test/aspireCodeLensProvider.test.ts Adds coverage ensuring resource lenses aren’t emitted using state from a different running AppHost.
extension/src/test/appHostDataRepository.test.ts Adds coverage for multi-AppHost auto-switch to global polling and new describe-watch compatibility error scenarios.

Comment thread extension/src/views/AppHostDataRepository.ts Outdated
Comment thread extension/src/views/AppHostDataRepository.ts Outdated
adamint and others added 2 commits May 6, 2026 15:38
Update the describe --follow no-data comment so it matches the intentional compatibility hint behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match CodeLens resource scoping for gutter decorations so stopped AppHost files do not show state from another running AppHost with the same resource names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl
Copy link
Copy Markdown
Contributor

There’s a new command I want this to use which is aspire ls

Switch extension AppHost discovery to aspire ls JSON output and use candidate status to ignore possibly-unbuildable AppHosts for workspace/global mode decisions. Also avoid showing the AppHost describe compatibility banner when describe exits successfully without resource data.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adamint adamint marked this pull request as ready for review May 19, 2026 19:25
adamint and others added 3 commits May 19, 2026 15:47
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adamint adamint marked this pull request as draft May 19, 2026 20:46
@adamint adamint changed the title Default VS Code extension to global view for multiple AppHosts Support live workspace AppHost updates May 19, 2026
@adamint adamint marked this pull request as ready for review May 19, 2026 23:00
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Matched test failure patterns (26 tests)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.TracingEnablesTheRightActivitySource — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.TracingEnablesTheRightActivitySource_Keyed — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.OptionsTypeIsSealed — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.HealthChecksRegistersHealthCheckService(enabled: True) — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.HealthChecksRegistersHealthCheckService(enabled: False) — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.EachKeyedComponentRegistersItsOwnHealthCheck — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.TracingRegistersTraceProvider(enabled: True) — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.TracingRegistersTraceProvider(enabled: False) — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.MetricsRegistersMeterProvider(enabled: True) — MCR container pull denied (rate limiting)
  • Aspire.Microsoft.Data.SqlClient.Tests.ConformanceTests.MetricsRegistersMeterProvider(enabled: False) — MCR container pull denied (rate limiting)
  • ...and 16 more

@adamint adamint requested a review from karolz-ms as a code owner May 20, 2026 03:27
@adamint adamint requested a review from danegsta as a code owner May 20, 2026 03:27
adamint added 3 commits May 19, 2026 23:51
# Conflicts:
#	src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs
#	tests/Aspire.Cli.Tests/Backchannel/ResourceSnapshotMapperTests.cs
@IEvangelist
Copy link
Copy Markdown
Member

PR Testing Report

PR Information

CLI Version Verification

  • Expected commit: 92d21318adb8368efcae68d37d079eb9cf0c81ef
  • Installed CLI: 13.4.0-pr.16836.g92d21318
  • Status: Passed - the dogfood CLI matched the PR head commit suffix.

Changes Analyzed

The PR changes VS Code extension live AppHost/resource update handling and CLI backchannel/aspire ps behavior, including JSON resource output and follow support.

Test Scenarios Executed

Scenario 1: Dogfood CLI install and version check

Objective: Verify the PR dogfood artifact is available and corresponds to the current PR head.

Result: Passed

Evidence:

Scenario 2: Fresh AppHost lifecycle and aspire ps table output

Objective: Verify a fresh app can be created, built, started detached, and listed by aspire ps.

Steps:

  1. Ran aspire new aspire-starter --name PsResourceSmoke --output . --non-interactive.
  2. Ran dotnet build .\PsResourceSmoke.AppHost\PsResourceSmoke.AppHost.csproj --nologo.
  3. Ran aspire run --project .\PsResourceSmoke.AppHost\PsResourceSmoke.AppHost.csproj --detach --non-interactive --nologo.
  4. Ran aspire ps --non-interactive --nologo.

Result: Passed

Evidence:

  • Template version selected: 13.4.0-pr.16836.g92d21318.
  • Build result: Build succeeded. with 0 warnings and 0 errors.
  • Detached AppHost started successfully.
  • aspire ps table output listed the running AppHost.

Scenario 3: aspire ps --format json --resources parseability

Objective: Verify the new JSON resource output can be consumed directly as JSON.

Steps:

  1. With the detached AppHost running, ran aspire ps --format json --resources --non-interactive --nologo.
  2. Attempted to parse stdout as JSON.

Result: Failed

Failure: stdout started with human-readable status text before the JSON payload:
ext Scanning for running AppHosts... [ { "appHostPath": "...PsResourceSmoke.AppHost.csproj", "appHostPid": 84432, "sdkVersion": "13.4.0-pr.16836.g92d21318", ...

Because of the leading Scanning for running AppHosts... line, direct ConvertFrom-Json parsing failed:
ext Conversion from JSON failed with error: Unexpected character encountered while parsing value: S. Path '', line 0, position 0.

The JSON body itself included resource details for apiservice and webfrontend, but the command output is not machine-readable JSON as emitted.

Summary

Scenario Status Notes
Dogfood CLI install/version check Passed Artifact version matched PR head commit suffix.
Fresh AppHost lifecycle and table ps output Passed Create/build/start/list/stop worked.
ps --format json --resources parseability Failed Human-readable status text is emitted before the JSON payload.

Overall Result

Issue found. The new JSON/resource output path returns resource data, but stdout is not directly parseable as JSON because aspire ps writes a status line before the JSON payload.

Avoid rendering scan status while producing JSON output so non-interactive ps output remains parseable. Add regression coverage and lifecycle tests for follow/process cleanup paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adamint adamint requested a review from IEvangelist May 21, 2026 03:16
function createAppHostProjectSearchResult(appHostCandidates: AppHostCandidate[], selectedProjectFile: string | null): AppHostProjectSearchResult {
const buildableCandidates = appHostCandidates.filter(isBuildableAppHostCandidate);
const allProjectFileCandidates = buildableCandidates.map(candidate => candidate.path);
const selectedCandidate = selectedProjectFile && buildableCandidates.some(candidate => isSamePath(candidate.path, selectedProjectFile))
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.

What happens if an existing workspace has multiple buildable AppHosts but its configured appHost.path points outside the aspire ls result set, for example to a sibling/parent AppHost or one otherwise excluded from ambient discovery? The old extension get-apphosts path intentionally preserved that configured selection and added it to all_project_file_candidates even when it was outside the discovered list, but this refactor drops it here unless it matches one of the buildable aspire ls candidates. In that case the extension loses the user's default AppHost, never starts describe --follow --apphost for it, and workspace mode filters ps down to the wrong candidate set. This silently breaks brownfield workspaces that relied on a valid configured AppHost outside the ambient search results. The fix direction is to preserve a configured selected path even when it is not in appHostCandidates (and include it in the candidate paths used downstream), matching the old ProjectLocator contract.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 5bc88da. Configured AppHost paths from aspire.config.json/.aspire/settings.json are now preserved even when they are outside the aspire ls result set, and included in the downstream candidate paths used by workspace describe/ps handling. Added regression coverage for discovery and repository workspace behavior.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented May 22, 2026

Scenario 3: aspire ps --format json --resources parseability

Failure: stdout started with human-readable status text before the JSON payload:
ext Scanning for running AppHosts... [ { "appHostPath": "...PsResourceSmoke.AppHost.csproj", "appHostPid": 84432, "sdkVersion": "13.4.0-pr.16836.g92d21318", ...

This is weird. When format is JSON then all non-JSON content should automatically go to stderr. I assume this must work when run during manual testing.

adamint and others added 2 commits May 21, 2026 23:21
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wait for the async workspace AppHost discovery state instead of assuming a single timer/microtask flush is enough. This avoids the Windows CI race where the assertion can run before vscode.workspace.fs.readFile completes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +100 to +103
private static readonly Option<bool> s_includeHiddenOption = new("--include-hidden")
{
Description = SharedCommandStrings.IncludeHiddenOptionDescription
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why does ps now include resources?

Why not use describe to get resources?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

describe works well once we have a specific AppHost, and we still use it for workspace-mode resource streaming. The global view needs one ambient command that can return every running AppHost with its resources; doing that with describe would mean ps first, then one describe process per AppHost, plus reconciling AppHost add/remove races across those streams.

I’ll keep ps --resources for that scenario and make the streaming contract clearer with the follow-up change.

Copy link
Copy Markdown
Member

@JamesNK JamesNK May 22, 2026

Choose a reason for hiding this comment

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

But I don't think ps should include resources. We have something that streams resources today and that's describe.

then one describe process per AppHost

Which will probably either be 0 or 1. You'll need to support more running at once, but that won't be typical. Having two ongoing CLI calls doesn't sound like a big deal to me.

@adamint
Copy link
Copy Markdown
Member Author

adamint commented May 22, 2026

PR testing results

I ran the PR testing workflow against the dogfood CLI for this PR.

Result: Issues found. The PR-specific ps JSON/resource behavior passed, but a starter-template smoke failed to build from the PR hive.

Passed

  • Dogfood CLI installed successfully after the macOS ARM64 artifact became available.
  • Installed CLI version matched the PR head: 13.4.0-pr.16836.gde27b719 for de27b719c904a9c9d35bf383d0a5c283ebf477ae.
  • Fresh aspire-empty app from the PR hive started successfully with an executable resource.
  • aspire ps --format json --resources emitted valid JSON only, with no human-readable status text on stdout.
  • aspire ps --follow --format json --resources emitted a valid first JSON snapshot with resource data and no status text contamination.

Failed

  • Fresh aspire-starter creation succeeded, but aspire start failed during build:
/private/tmp/a16836test/scenario-starter-smoke/Pr16836Starter/Pr16836Starter.Web/Program.cs(2,26): error CS0234: The type or namespace name 'Components' does not exist in the namespace 'Pr16836Starter.Web'

This appears outside the PR’s changed CLI/extension area, but it is a dogfood smoke failure from the PR hive in this local environment.

Comment thread src/Aspire.Cli/Commands/PsCommand.cs Outdated
Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

Noting the streaming pattern inconsistency between ps --follow (full snapshots) vs describe --follow / logs --follow (incremental per-item NDJSON).

Comment thread src/Aspire.Cli/Commands/PsCommand.cs Outdated
Comment on lines +323 to +330
async Task WriteSnapshotAsync()
{
var appHostInfos = await GatherAppHostInfosAsync(currentConnections, includeResources, includeHidden, followCancellationToken).ConfigureAwait(false);
var json = JsonSerializer.Serialize(appHostInfos, PsCommandJsonContext.CompactRelaxedEscaping.ListAppHostDisplayInfo);
if (!string.Equals(json, lastJson, StringComparison.Ordinal))
{
lastJson = json;
_interactionService.DisplayRawText(json, ConsoleOutput.Standard);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Inconsistency with other --follow commands.

ps --follow emits a full JSON array snapshot of all AppHosts (with all their resources) on every change, while the existing --follow implementations stream incrementally:

  • describe --follow: NDJSON where each line is a single ResourceJson for the resource that changed (per-resource delta).
  • logs --follow: NDJSON where each line is a single log entry as it arrives.
  • ps --follow (this PR): NDJSON where each line is the entire List<AppHostDisplayInfo> array re-serialized.

I understand the motivation — the extension consumer replaces its entire _appHosts array on each line, so full snapshots simplify the client. But this creates an inconsistent streaming contract across the CLI:

  1. Consumers of describe --follow must maintain a map and merge per-resource updates; consumers of ps --follow get the full truth on every line. Should these commands follow the same pattern for consistency?
  2. As the number of AppHosts/resources grows, re-serializing everything on each single resource state change (e.g., a health check toggling) could produce large repeated output. describe --follow avoids this by only emitting the changed resource.

Worth considering whether ps --follow should either:

  • Stream per-AppHost deltas (like describe streams per-resource), or
  • Document explicitly that this command intentionally uses full-snapshot semantics and why (so future maintainers don't try to "fix" it to match describe).

At minimum, a code comment here explaining the design choice (full snapshot for simpler client-side replacement) would help.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you can fix this by streaming just app host instances that are new or change, and have a column for status. e.g. running/stopped

Because app hosts can disappear (i.e. be stopped) then that must be communicated to consumers which can be done by returning the app host one last time with a status of stopped.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, I’ll change ps --follow to stream per-AppHost updates instead of full snapshots. I’ll add a status field so removed AppHosts can be emitted once more as stopped, and resource changes can emit the owning AppHost with updated resources.

@adamint
Copy link
Copy Markdown
Member Author

adamint commented May 22, 2026

Fixed the JSON stdout contamination in the current push. ps --format json now skips the status renderer and scans directly, so the output is parseable JSON only. I also verified ps --format json --resources and the first ps --follow --format json --resources snapshot against the dogfood CLI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adamint adamint requested a review from JamesNK May 22, 2026 17:32
adamint and others added 2 commits May 22, 2026 14:37
@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented May 25, 2026

#16836 (comment)

@mitchdenny
Copy link
Copy Markdown
Member

Screenshot 2026-05-25 at 2 50 47 pm

I got this working locally just fine. Took me a moment to figure out how to flick into the global vs. home view. The onther thing that caught me out was that if I create an app host in two sub directories and launch each of then, then open vscode rooted on the directory above, it doesn't show them unless I flip to the global view.

As a follow up you could make it automatically detect that sub-folders have apphosts running (and there isn't a single apphost in the current immediate scope) and then flip to show that workspace view. It might be that we need three views.

Global, Tree, Immediate dir (terms aren't exactly what I'm going for but you get the idea).

Resolve conflicts from main reverting parser-backed AppHost parsing
(microsoft#17413). Take main's regex-based csharp parser and drop the
tree-sitter helpers + orphan emscripten typedef, while preserving
this branch's workspace-AppHost-aware gutter/lens filtering and new
tests (now sync to match the reverted parser interface).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl
Copy link
Copy Markdown
Contributor

I made some changes to how the extension finds apphosts here #17408. I think this logic watch the workspace and re-run ls to avoid having the logic for "what is an apphost" be in multiple places.

Resolve XLF conflicts from PR microsoft#17459 (Remove Logs column from
aspire ps). Drop HeaderCliLog trans-units from every locale and
keep this branch's FollowOptionDescription / FollowRequiresJson
additions; the orphan HeaderCliLog resx/Designer entries had
already been auto-merged away.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny mitchdenny merged commit be07f42 into microsoft:main May 25, 2026
616 of 619 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 25, 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.

[Extension] Panel and CodeLens don't work in workspace mode with multiple nested AppHosts; auto-switch to global view

7 participants