Skip to content

Detect an outdated Aspire VS Code extension in aspire doctor - #19128

Closed
Adam Ratzman (adamint) wants to merge 43 commits into
microsoft:mainfrom
adamint:adamint/18694-doctor-vscode-extension-version
Closed

Detect an outdated Aspire VS Code extension in aspire doctor#19128
Adam Ratzman (adamint) wants to merge 43 commits into
microsoft:mainfrom
adamint:adamint/18694-doctor-vscode-extension-version

Conversation

@adamint

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

Copy link
Copy Markdown
Member

Description

Teach aspire doctor to distinguish a current, outdated, missing, and unknown-version Aspire VS Code extension.

The running extension contributes its authoritative version, stable/prerelease channel, and product/gallery source to terminals, tasks, debug processes, and stdio MCP servers so doctor can inspect the instance VS Code actually activated. Packaged builds derive the channel from the VSIX manifest, including offline stable and prerelease packages. The source signal prevents VSCodium, code-oss, and unknown galleries from triggering Microsoft Marketplace lookup or links.

MCP definitions pass only the ASPIRE_CLI_* identity overrides, leaving inherited PATH and unrelated environment values out of VS Code's cached server definition. A reported active-extension version is sufficient to run the check even when a remote or desktop extension host has no code launcher or VS Code terminal marker.

Outside an extension-host terminal, doctor falls back to installed extension directories and reports the channel as unknown rather than guessing or making an unusable Marketplace request.

The check now:

  • detects VS Code from shell launchers and standard macOS application locations
  • parses the installed extension version defensively
  • compares stable and prerelease installs against the matching Marketplace channel
  • reports missing Marketplace versions separately from unavailable Marketplace access
  • preserves a useful installed/unknown result when only disk discovery is available

Validation

  • Extension unit suite: 1,461 passing, 4 pending.
  • Packaged offline stable and prerelease E2E suites: 7 passing each.
  • Focused CLI doctor coverage: 34 passing.
  • Extension test compilation, E2E compilation, webpack compile/package, ESLint, and diff checks pass.

Checklist

  • Feature is complete.
  • Tests cover the changed behavior.
  • No public API was added.
  • No new security guarantee is introduced.

Copilot AI balanced review requested due to automatic review settings August 7, 2026 13:49
@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 -- 19128

Or

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

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

Extends aspire doctor to detect outdated Aspire VS Code extensions through a bounded, anonymous Marketplace lookup.

Changes:

  • Detects active extension roots, versions, channels, sources, and obsolete installations.
  • Compares gallery installations with stable or pre-release Marketplace versions.
  • Adds diagnostics, localization, documentation, and comprehensive tests.
Show a summary per file
File Description
src/Aspire.Cli/Program.cs Registers the Marketplace client.
src/Aspire.Cli/Utils/EnvironmentChecker/IVsCodeExtensionMarketplaceClient.cs Defines the Marketplace abstraction.
src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs Queries and parses Marketplace versions.
src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs Detects installations and reports updates.
src/Aspire.Cli/Resources/DoctorCommandStrings.resx Adds user-facing messages.
src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs Exposes generated resource properties.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.cs.xlf Updates Czech localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.de.xlf Updates German localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.es.xlf Updates Spanish localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.fr.xlf Updates French localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.it.xlf Updates Italian localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.ja.xlf Updates Japanese localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.ko.xlf Updates Korean localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.pl.xlf Updates Polish localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.pt-BR.xlf Updates Brazilian Portuguese localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.ru.xlf Updates Russian localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.tr.xlf Updates Turkish localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.zh-Hans.xlf Updates Simplified Chinese localization data.
src/Aspire.Cli/Resources/xlf/DoctorCommandStrings.zh-Hant.xlf Updates Traditional Chinese localization data.
tests/Aspire.Cli.Tests/Utils/TestVsCodeExtensionMarketplaceClient.cs Adds a configurable test client.
tests/Aspire.Cli.Tests/Utils/MockHttpMessageHandler.cs Supports asynchronous HTTP responses.
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Wires the test Marketplace service.
tests/Aspire.Cli.Tests/Commands/VsCodeExtensionMarketplaceClientTests.cs Tests requests, parsing, limits, and cancellation.
tests/Aspire.Cli.Tests/Commands/VsCodeExtensionCheckTests.cs Covers detection and update decisions.
tests/Aspire.Cli.Tests/Commands/DoctorCommandTests.cs Verifies human-readable and JSON output.
tests/Aspire.Cli.EndToEnd.Tests/DoctorCommandTests.cs Isolates ambient VS Code installations.
docs/specs/cli-output-formats.md Documents the expanded JSON contract.

Review details

Files not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (4)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:594

  • A valid JSON array containing a non-object entry (or a non-object identifier) throws InvalidOperationException here. Since ReadExtensionsIndex does not catch that exception, one malformed VS Code index entry causes the entire environment check to disappear rather than falling back to folder metadata.
        if (!entry.TryGetProperty("identifier", out var identifier) ||
            !identifier.TryGetProperty("id", out var id) ||
            !string.Equals(id.GetString(), ExtensionId, StringComparison.OrdinalIgnoreCase) ||
            !entry.TryGetProperty("relativeLocation", out var relativeLocationElement) ||
            relativeLocationElement.ValueKind != JsonValueKind.String)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs:220

  • A syntactically valid response with an unexpected shape (for example [] or a null item in results) makes these TryGetProperty/GetString calls throw InvalidOperationException. That exception is intentionally treated as an implementation failure by VsCodeExtensionCheck, so the doctor row disappears instead of reporting Marketplace unavailability. Validate object/string kinds throughout the response or translate schema-shape failures to InvalidDataException.
        if (root.TryGetProperty("results", out var results) &&
            results.ValueKind == JsonValueKind.Array)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:676

  • A syntactically valid but non-object package.json (for example []) makes TryGetProperty throw InvalidOperationException, bypassing the intended fallback to the folder version and dropping the doctor check. Guard the root kind before reading version.
            return document.RootElement.TryGetProperty("version", out var versionElement) &&

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:534

  • The index at the extension root is only the default VS Code profile's manifest; named profiles store their own extensions.json under profile user data. If Aspire is installed only in the active named profile, the folder is found but its source/release-channel metadata is absent, so ShouldCheckMarketplace always suppresses the new update check. Conversely, a copy belonging only to another profile can be reported as active.
        var indexPath = Path.Combine(extensionsDirectory, "extensions.json");
  • Files reviewed: 26/27 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs Outdated
Comment thread src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs 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.

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (5)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:558

  • extensionsDirectory/extensions.json is only VS Code's default-profile manifest; non-default profiles have separate extensions.json resources while sharing the same physical extension folders. In a custom profile this can therefore attach default-profile source/channel metadata—or report an extension as installed at all—even when the running profile does not load it, producing an incorrect Marketplace warning. Resolve the active profile's manifest before trusting this metadata, or suppress the comparison when the active profile cannot be identified.
        var indexPath = Path.Combine(extensionsDirectory, "extensions.json");

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:632

  • This uses metadata.isPreReleaseVersion, which describes the currently installed artifact, not the update channel selected by the user. VS Code persists that selection in metadata.preRelease and uses extension.preRelease for its own update queries. When a pre-release subscription temporarily has a stable artifact (for example, due to compatibility fallback), doctor compares against the stable feed and can miss the available pre-release update. Parse preRelease for channel selection, using isPreReleaseVersion only as an artifact property/fallback.
            if (metadata.TryGetProperty("isPreReleaseVersion", out var preReleaseElement) &&
                preReleaseElement.ValueKind is JsonValueKind.True or JsonValueKind.False)
            {
                isPreReleaseVersion = preReleaseElement.GetBoolean();

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs:70

  • A syntactically valid response with an unexpected JSON shape (for example, [], or a non-object entry in results) makes the TryGetProperty/GetString calls in ParseVersions throw InvalidOperationException. VsCodeExtensionCheck does not classify that as Marketplace unavailability, so EnvironmentChecker silently drops the entire check instead of emitting the documented warning. Translate malformed-shape failures to InvalidDataException (or validate every ValueKind) so they follow the unavailable path.
            return ParseVersions(responseBytes);

tests/Aspire.Cli.Tests/Commands/DoctorCommandTests.cs:149

  • Repository test guidance explicitly avoids Assert.DoesNotContain for generated/structured output because it can pass while the rest of the output regresses (AGENTS.md:267-268). Verify the complete rendered output, preferably with a Verify snapshot, so this test pins both the redaction and the surrounding doctor result.
        Assert.DoesNotContain(rawFailure, rendered, StringComparison.Ordinal);

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:482

  • Selecting the highest physical folder does not identify the version loaded by a VS Code profile. Profiles share the extension directory and their manifests reference a specific relativeLocation/version, so another profile can legitimately keep a newer copy that is not active here. This can publish the wrong installed version or suppress the Marketplace check because the selected folder has no metadata in the current manifest. When a profile manifest is available, select its Aspire entry rather than the maximum folder version; only use folder ordering as a legacy fallback.
        var selectedExtension = installedExtensions
            .Where(extension => extension.Version is not null)
            .OrderByDescending(
                extension => extension.Version,
                SemVersionPrecedenceComparer.Instance)
            .FirstOrDefault()
  • Files reviewed: 27/28 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@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.

Copilot AI review requested due to automatic review settings August 7, 2026 17: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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (5)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:256

  • Because this environment variable is introduced by this PR, every extension version that is already outdated lacks it. In that upgrade scenario detection only finds the on-disk folder, CheckAsync takes the no-version pass path, and the Marketplace is never queried, so the new CLI cannot flag the pre-feature versions this change is meant to detect. Retain a disk-version fallback when one active root can be identified, using this variable only as the higher-authority signal.
        var reportedVersion = environment.GetEnvironmentVariable(ExtensionVersionEnvironmentVariable);
        if (!string.IsNullOrWhiteSpace(reportedVersion))

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:144

  • A VSIX or sideloaded build also contributes its packageJSON.version, so this gate queries the Marketplace for those installs and can tell users of an older private/local build to replace it. That contradicts the stated contract that non-gallery installs have no actionable Marketplace counterpart. Carry install-source provenance from the extension or disk detection and require gallery before querying.
        if (!updateCheckEnabled ||
            !SemVersion.TryParse(detection.ExtensionVersion, SemVersionStyles.Strict, out var installedVersion))

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs:70

  • Marketplace JSON is external input, but ParseVersions calls JsonElement.GetString() without consistently checking ValueKind. A valid JSON response such as a numeric extensionName, publisherName, property key, or property value throws InvalidOperationException; VsCodeExtensionCheck does not handle that exception, so EnvironmentChecker silently drops this check instead of returning the documented unavailable warning. Normalize malformed-shape exceptions to InvalidDataException.
            return ParseVersions(responseBytes);

extension/src/extension.ts:84

  • The new activation-to-child-process contract is only tested by calling the helper with a fake collection; the CLI tests then inject the variable manually. No extension E2E test proves that activating the real extension makes the version visible in an integrated terminal, so the feature's sole version signal can regress without either test suite noticing. Add an extension E2E test that activates the extension, opens a terminal, and verifies this environment value or the resulting aspire doctor behavior.
  syncAspireExtensionVersionEnvironment(context.environmentVariableCollection, context.extension.packageJSON?.version);

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:187

  • The release channel cannot be inferred from SemVer here. VS Code publishes pre-releases via a Marketplace flag and officially recommends ordinary odd-minor versions (for example 1.15.0), while this repository's publish path uses vsce --pre-release (eng/pipelines/release-publish-nuget.yml:2288-2291). Such an installed pre-release has IsPrerelease == false and is compared with the stable feed, so an outdated pre-release can be reported current. Propagate the actual Marketplace pre-release metadata and skip lookup when the channel is unknown.
        var channel = installedVersion.IsPrerelease ? PreReleaseChannel : StableChannel;
        var latestVersion = installedVersion.IsPrerelease ? versions.PreReleaseVersion : versions.StableVersion;
  • Files reviewed: 32/33 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@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.

Copilot AI review requested due to automatic review settings August 7, 2026 18:29

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (4)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:152

  • Nothing before this request establishes that the running extension came from the Marketplace. VSIX and sideloaded builds also execute the new activation code and contribute a parseable manifest version, so an older local build will be queried and receive an actionable “update from Marketplace” warning. This contradicts the PR’s stated gallery-only gating; carry the installation source with the extension signal (or determine it reliably) and skip the lookup for non-gallery installs.
        VsCodeExtensionMarketplaceVersions versions;
        try
        {
            versions = await _marketplaceClient.GetLatestVersionsAsync(cancellationToken);

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs:223

  • A syntactically valid but structurally malformed Marketplace response can throw InvalidOperationException here (TryGetProperty requires an object), and the same applies to nested result, candidate, and property elements. VsCodeExtensionCheck only translates JSON/I/O/HTTP failures, so EnvironmentChecker swallows this exception and the entire check disappears instead of reporting the documented unavailable warning. Validate each ValueKind and convert shape violations to InvalidDataException.
    private static bool TryFindExtension(JsonElement root, out JsonElement extension)
    {
        if (root.TryGetProperty("results", out var results) &&
            results.ValueKind == JsonValueKind.Array)
        {

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:187

  • IsPrerelease cannot identify actual Marketplace pre-release installs here. extension/Extension.proj:54 publishes them with VSCE’s --pre-release flag while the manifest version remains an ordinary x.y.z, so the contributed packageJSON.version is not a SemVer prerelease and this always selects stable. Such users will never be compared with newer pre-release versions. Propagate the Marketplace channel separately from the version instead of inferring it from SemVer.
        var channel = installedVersion.IsPrerelease ? PreReleaseChannel : StableChannel;
        var latestVersion = installedVersion.IsPrerelease ? versions.PreReleaseVersion : versions.StableVersion;

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:407

  • This folder-name-only test never consults the extensions root’s .obsolete file. Consequently, an extension folder that VS Code has marked obsolete and is awaiting deletion is still reported as installed, despite the PR description explicitly promising those entries are skipped. Load the obsolete folder names for each root and exclude them before accepting a match.
        return folderName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) &&
            folderName.Length > prefix.Length &&
            char.IsAsciiDigit(folderName[prefix.Length]);
  • Files reviewed: 32/33 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs Outdated
Adam Ratzman (adamint) pushed a commit to adamint/aspire that referenced this pull request Aug 7, 2026
The npm subprocess was spawned and then configured to behave exactly like a
plain HTTPS GET. RemoveAmbientNpmConfiguration stripped NPM_CONFIG_*, empty
--userconfig/--globalconfig files replaced the user and global layers, a marker
package.json pinned the local prefix, and --cache pointed at a throwaway
directory. Everything npm could contribute over an HttpClient was deliberately
disabled, and the process plumbing that remained produced the scoped-registry,
inherited-json, unbounded-Dispose, and descendant-pipe-retention defects raised
on this change.

Replace it with INpmRegistryClient: one GET of the package document, read
dist-tags.latest, parse. This follows SigstoreNpmProvenanceChecker in the same
folder, which already reads registry.npmjs.org with an injected HttpClient, and
matches the client shape in microsoft#19128 so the two merge mechanically.

Target the public npm registry rather than the dnceng mirror. The advertised
remedy is "npm install -g @microsoft/aspire-cli@latest", which npm resolves
through the user's own registry configuration, defaulting to public npm. The
mirror is a downstream cache that only Playwright acquisition reads, so checking
it made the notification a lagging proxy for a source the update never touches.

Removing the subprocess also removes the npm-on-PATH requirement: the update
check no longer warns on machines without the Node toolchain, and
CliUpdateNotifier no longer needs IDisposable or a blocking shutdown drain.

The release-pipeline mirror seeding gate is no longer needed by this change and
moves to its own branch.

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

@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.

@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.

Copilot AI review requested due to automatic review settings August 7, 2026 19:38

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (6)

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:426

  • This does the opposite of the PR's stated platform-suffix handling: it rejects 1.2.3-darwin-arm64 as a pre-release instead of stripping the target-platform suffix before parsing. If a valid platform-specific installation has a missing or temporarily unreadable manifest, doctor emits an unknown-version warning rather than recovering stable version 1.2.3. Strip recognized VS Code target-platform suffixes before the fallback parse.
        if (SemVersion.TryParse(versionSegment, SemVersionStyles.Strict, out var folderVersion) &&
            !folderVersion.IsPrerelease &&
            folderVersion.Metadata.Length == 0)

src/Aspire.Cli/Utils/VsCodeInstallLayout.cs:71

  • The active-root resolution described by the PR is missing here. Apart from VSCODE_EXTENSIONS, this ignores VSCODE_AGENT_FOLDER, the remote IPC/client signals, and the desktop askpass path, then scans every default root. With desktop and remote installs present, doctor can therefore compare an inactive version instead of treating the installation as ambiguous. Resolve the authoritative signals first and represent multiple matching fallback roots as unknown.
        var overrideDirectory = environment.GetEnvironmentVariable("VSCODE_EXTENSIONS");
        if (!string.IsNullOrWhiteSpace(overrideDirectory))
        {
            yield return overrideDirectory;
            yield break;

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:177

  • This lookup runs for every parseable installed version because detection carries no gallery/VSIX source. Consequently, an older sideloaded or locally built extension is compared with the Marketplace and receives an update warning even though it has no Marketplace counterpart, contrary to the stated query gating. Track the installation source and only query for an identifiable gallery install.

This issue also appears on line 424 of the same file.

        VsCodeExtensionMarketplaceVersions versions;
        try
        {
            versions = await _marketplaceClient.GetLatestVersionsAsync(cancellationToken);

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:394

  • These candidates are yielded without consulting the root's .obsolete file. VS Code records superseded or pending-deletion extension folders there, so an uninstalled/obsolete Aspire folder can still be reported as installed and compared. Load the obsolete entries for each root and exclude matching folders before resolving versions.
                if (IsVersionedExtensionFolder(Path.GetFileName(current)))
                {
                    yield return current;

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs:185

  • Structurally valid JSON with an unexpected shape is not handled as an unavailable Marketplace response. For example, a top-level [], a non-object results entry, or a non-string publisher causes TryGetProperty/GetString to throw InvalidOperationException; VsCodeExtensionCheck does not catch that type, so EnvironmentChecker silently drops the entire row instead of returning the documented warning. Validate element kinds throughout parsing or normalize shape errors to InvalidDataException.
        using var document = JsonDocument.Parse(responseBytes);
        if (!TryFindExtension(document.RootElement, out var extension))

tests/Aspire.Cli.EndToEnd.Tests/DoctorCommandTests.cs:165

  • The isolation command leaves the newly introduced ASPIRE_VSCODE_EXTENSION_VERSION untouched. When these E2E tests are launched from an Aspire-extension-created environment, detection trusts that ambient value before inspecting the empty override root and contacts the live Marketplace, defeating this helper's determinism. Unset the version variable here as well.
            """mkdir -p "$PWD/.doctor-vscode-extensions" && export VSCODE_EXTENSIONS="$PWD/.doctor-vscode-extensions" && unset VSCODE_AGENT_FOLDER VSCODE_GIT_ASKPASS_NODE VSCODE_GIT_ASKPASS_MAIN VSCODE_CLIENT_COMMAND""",
  • Files reviewed: 32/33 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@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.

Adam Ratzman and others added 3 commits August 7, 2026 16:19
Detect the installed Aspire VS Code extension version, ignore obsolete
installs, and compare it with the latest stable Marketplace version.
Keep installed-extension checks passing when Marketplace lookup fails while
surfacing diagnostic details and metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ffeff87e-f284-434d-87d3-843e21a7aebb
Only compare against the Marketplace when the installed extension can be
attributed to a single active extension root, a gallery source, and a known
release channel, and honor the update-notification feature switch. Compare
pre-release installs against the pre-release feed instead of the stable one.

Fix the Marketplace request itself: the gallery is an Azure DevOps service and
rejects a query with HTTP 400 unless the Accept header names an API version, so
the lookup previously never succeeded. Drop the X-Market-Client-Id and
X-Market-User-Id headers, which the anonymous query does not need and which
would have made the CLI emit a VS Code installation identifier.

Report a warning with diagnostic details when the lookup times out or is
unavailable, and document the resulting JSON contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32c21d87-2f53-4647-bf1c-c5ca64abf14a
Address two review findings on the doctor VS Code extension check.

The Marketplace client requests with HttpCompletionOption.ResponseHeadersRead,
so the response body is still streaming after SendAsync returns. The private
timeout translation only wrapped SendAsync, so a server that answered with
headers and then stalled cancelled the body read with the timeout token and
surfaced a bare OperationCanceledException. Doctor drops a check on
cancellation, so the documented timeout warning was never reported. Move the
body read and parse inside the same translation.

Extension root detection also ignored portable mode. VS Code resolves the
extension root as --extensions-dir, VSCODE_EXTENSIONS, VSCODE_PORTABLE/extensions,
then the home data folder, so a portable install fell through to the
home-directory defaults and doctor either reported the extension missing or
compared an unrelated installation against the Marketplace. Probe
VSCODE_PORTABLE after VSCODE_EXTENSIONS and before every default root.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Threads a reported extension source through the doctor test service setup
and asserts the JSON check metadata surfaces it, so the marketplace-vs-
sideloaded distinction is covered rather than only version and channel.

Recovered from an uncommitted working tree after the authoring session was
killed by a CLI out-of-memory crash on 2026-08-11. The worktree was on a
detached HEAD, so the work is preserved on a named branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f1bd51d5-cb9f-4370-a07b-c94416baad61
Copilot AI review requested due to automatic review settings August 11, 2026 20:46

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (5)

docs/specs/cli-output-formats.md:488

  • This metadata contract omits extensionSource and incorrectly says latestVersionKnown is added by a Marketplace lookup. The implementation emits the source with any discovered version and sets latestVersionKnown: false before the unknown-channel/source gates, even when no lookup occurs. Document those fields at the points where they are actually emitted.
Its `metadata` always exposes `vsCodeInstalled` (bool), `extensionInstalled` (bool), and `extensionId` (string). An installed extension can add `extensionVersion` (string), `extensionVersionKnown` (bool), and `extensionChannel` (`stable`, `prerelease`, or `unknown`). A Marketplace lookup adds `latestVersionKnown` (bool). A successful applicable comparison also adds `latestVersion` (string), `latestVersionChannel` (`stable` or `prerelease`), and `updateAvailable` (bool); an unavailable lookup adds `latestVersionError` with the value `unavailable`.

docs/specs/cli-output-formats.md:490

  • This lists only two contributed signals, but the extension also contributes ASPIRE_VSCODE_EXTENSION_SOURCE, and doctor relies on it to suppress Microsoft Marketplace requests and links for VSCodium, Code - OSS, and unknown galleries. Documenting only version/channel leaves out a required part of the protocol.
The Aspire VS Code extension contributes `ASPIRE_VSCODE_EXTENSION_VERSION` and `ASPIRE_VSCODE_EXTENSION_CHANNEL` to terminals created by VS Code. Those signals identify the active extension instance and select the matching Marketplace channel; Marketplace prerelease versions use ordinary `major.minor.patch` versions, so the version string alone cannot identify the channel. When the signals are unavailable, doctor falls back to the existing known extension roots and reads the version from `package.json` or the versioned extension folder, but reports the channel as `unknown`.

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionCheck.cs:214

  • The disk fallback cannot find pre-signal VSCodium/Code - OSS installs because GetExtensionDirectories only scans the .vscode* roots. VSCodium stores extensions under ~/.vscode-oss/extensions, while its integrated terminal still sets TERM_PROGRAM=vscode; this branch therefore reports the extension as missing and returns the Microsoft Marketplace link instead of preserving an installed/unknown result. Include the OSS roots while keeping their source unknown, or otherwise avoid the Marketplace link when product identity is unavailable.
    docs/specs/cli-output-formats.md:469
  • The documented JSON example omits extensionSource, although BuildMetadata always emits it whenever extensionVersion is present and the command-level test asserts it. Add the field so consumers copying this output contract see the actual shape.

This issue also appears in the following locations of the same file:

  • line 488
  • line 490
        "extensionChannel": "stable",
        "latestVersion": "1.16.0",

src/Aspire.Cli/Utils/EnvironmentChecker/VsCodeExtensionMarketplaceClient.cs:28

  • The Marketplace response parsing is covered, but the request test never inspects this payload. A regression in the extension ID, target, exclusion filter, or 65584 flags would make the live service stop returning the stable/prerelease pair while all current tests still pass. Extend GetLatestVersionsAsync_SendsTheAnonymousMarketplaceQuery to assert the JSON criteria and flags sent here.
  • Files reviewed: 40/41 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@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.

Adam Ratzman and others added 2 commits August 14, 2026 18:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8dfcd7cf-8ebb-4393-84ba-f53927dcb1ab

# Conflicts:
#	.github/workflows/tests.yml
#	extension/scripts/run-e2e.js
#	extension/src/extension.ts
#	extension/src/testing/e2eStateFileBridge.ts
#	extension/webpack.config.js
Account for the VS Code extension pre-release DefinePlugin while preserving the production E2E bridge gate, and assert the local E2E package carries both required build markers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8dfcd7cf-8ebb-4393-84ba-f53927dcb1ab
Copilot AI review requested due to automatic review settings August 14, 2026 22:23

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
Suppressed comments (4)

docs/specs/cli-output-formats.md:490

  • The metadata contract and environment-signal list omit the new extensionSource field and ASPIRE_VSCODE_EXTENSION_SOURCE variable, even though they gate Marketplace lookup and are emitted for every detected version. Document the field's microsoft-marketplace/other/unknown values and the third signal.
Its `metadata` always exposes `vsCodeInstalled` (bool), `extensionInstalled` (bool), and `extensionId` (string). An installed extension can add `extensionVersion` (string), `extensionVersionKnown` (bool), and `extensionChannel` (`stable`, `prerelease`, or `unknown`). A Marketplace lookup adds `latestVersionKnown` (bool). A successful applicable comparison also adds `latestVersion` (string), `latestVersionChannel` (`stable` or `prerelease`), and `updateAvailable` (bool); an unavailable lookup adds `latestVersionError` with the value `unavailable`.

The Aspire VS Code extension contributes `ASPIRE_VSCODE_EXTENSION_VERSION` and `ASPIRE_VSCODE_EXTENSION_CHANNEL` to terminals created by VS Code. Those signals identify the active extension instance and select the matching Marketplace channel; Marketplace prerelease versions use ordinary `major.minor.patch` versions, so the version string alone cannot identify the channel. When the signals are unavailable, doctor falls back to the existing known extension roots and reads the version from `package.json` or the versioned extension folder, but reports the channel as `unknown`.

extension/src/utils/cliPathEnvironment.ts:273

  • This still classifies official VS Code as Microsoft Marketplace even when VS Code is configured to use a private gallery. Current VS Code registers extensions.gallery.serviceUrl and uses a non-empty value instead of the product's default gallery, so this path causes aspire doctor to query and link to Microsoft Marketplace despite the source guard. Include the configured gallery in the source classification and treat a non-Microsoft override as other/unknown.
    // VS Code does not expose its configured extension gallery URL. Require the matching
    // Microsoft product name and URI scheme so forks and side-loaded Code - OSS builds do not
    // direct users to the Microsoft Marketplace.
    return appName === 'Visual Studio Code' && uriScheme === 'vscode'
        || appName === 'Visual Studio Code - Insiders' && uriScheme === 'vscode-insiders';

.github/workflows/tests.yml:648

  • This marker is scoped only to the first package step. The production step below also passes --pre-release, but without this variable webpack bakes stable into that VSIX, so the artifact intended to represent the shipping production bundle has a prerelease manifest and a stable reported identity. Pass the marker to the production step while keeping only the E2E bridge opt-in scoped to this step.
          ASPIRE_VSCODE_EXTENSION_PACKAGE_PRERELEASE: 'true'

docs/specs/cli-output-formats.md:468

  • The documented outdated-extension JSON omits extensionSource, although this path always emits it and requires microsoft-marketplace to perform the shown comparison. Include the field so the example matches the output contract.

This issue also appears on line 488 of the same file.

        "extensionChannel": "stable",
  • Files reviewed: 41/42 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Resolved six conflicts. The central one is that this branch threads an
AspireExtensionEnvironment through the terminal provider and MCP server
constructors while main threads a CliPathResolver through the same
parameter slot. The two are disjoint contributions to the same child
environment (ASPIRE_VSCODE_EXTENSION_* versus AspireCliPath), so both are
kept rather than picking a side.

- AspireTerminalProvider: constructor now takes the resolver and the
  extension environment.
- AspireMcpServerDefinitionProvider: createAspireMcpServerDefinition takes
  the extension environment as a trailing parameter so main's existing call
  sites keep their argument order, and the produced environment composes the
  identity overrides with a forwardable AspireCliPath.
- cliSpawn/mcpServerDefinition tests: kept both sides' tests. The identity
  tests now stub path probing instead of relying on the real filesystem, so
  the expected environment no longer depends on the host.
sinon.createStubInstance replaces prototype methods but leaves accessors
alone, so a bare stub still ran the real aspireExtensionEnvironment getter.
That getter delegates to the terminal provider, which a stub instance never
receives, so the five AppHost launch tests that reach prepareDebugSession
threw while building the debug configuration.

Add a shared createFakeAspireDebugSession helper that shadows the accessor
with an own property, and use it wherever the dotnet debugger tests build a
session double so a future AppHost test does not rediscover this.
Copilot AI review requested due to automatic review settings August 19, 2026 06:08

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
  • Files reviewed: 43/44 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread .github/workflows/tests.yml
Comment thread docs/specs/cli-output-formats.md
Comment thread docs/specs/cli-output-formats.md Outdated
@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.

Adam Ratzman (adamint) and others added 8 commits August 19, 2026 05:00
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>
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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 19, 2026 10: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

Files not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
  • Files reviewed: 49/50 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread extension/src/utils/cliPathEnvironment.ts
@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.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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 not reviewed (1)
  • src/Aspire.Cli/Resources/DoctorCommandStrings.Designer.cs: Generated file
  • Files reviewed: 51/52 changed files
  • Comments generated: 1
  • Review effort level: Balanced

const extensionEnvironment = getAspireExtensionEnvironment(context.extension.packageJSON, {
appName: vscode.env.appName,
uriScheme: vscode.env.uriScheme,
extensionGalleryServiceUrl: vscode.workspace.getConfiguration().get<unknown>('extensions.gallery.serviceUrl'),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants