Add VS Code resource debugging for .NET and Go - #18602
Add VS Code resource debugging for .NET and Go#18602Adam Ratzman (adamint) wants to merge 85 commits into
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 18602Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 18602" |
There was a problem hiding this comment.
Pull request overview
This PR adds the first VS Code extension lane for #18538: a right-click Attach debugger action on running .NET project resources in the Aspire panel. It builds a CoreCLR attach debug configuration that targets the project's process by processName (best-effort resolving <AssemblyName> from the project file), rather than executable.pid, because DCP reports the dotnet run launcher PID rather than the child app process. The action is gated on a running top-level Project resource with a dotnet executable and a .csproj/.fsproj/.vbproj path, and only when the C# extension is installed; it re-resolves the latest resource before attaching so stale/ambiguous/missing resources fail closed with a warning.
Bundled alongside the feature is a broad telemetry overhaul: every event is renamed to its final wire name (aspire/vscode/... and aspire/dashboard/...) and routed through sendDangerousTelemetryEvent/sendDangerousTelemetryErrorEvent to bypass VS Code's automatic <extensionId>/ prefix, with manual opt-in gating (telemetryLevel), manual common properties, and explicit value sanitization.
Changes:
- Add
aspire-vscode.attachDebuggerToResourcecommand, menu contribution (canAttachDebuggercontext value), attachability helpers, and the attach execution flow with latest-resource re-resolution. - Rename all telemetry events to final wire names and route them through the "dangerous" send path with explicit gating/sanitization and manually-added common properties.
- Add localized strings and extensive unit tests for both the attach flow and the telemetry changes.
Show a summary per file
| File | Description |
|---|---|
extension/src/views/AspireAppHostTreeProvider.ts |
Core attach logic: attachability helpers, process-name derivation, attachDebuggerToResource, latest-resource resolution; propagates appHostPath through tree items. |
extension/src/extension.ts |
Registers the new attach command; renames the extension/activated telemetry event. |
extension/package.json |
Contributes the command, hidden palette entry, and view/item/context menu item; reorders action groups. |
extension/package.nls.json / extension/src/loc/strings.ts / extension/loc/xlf/aspire-vscode.xlf |
Adds/regenerates localized strings for the attach action. |
extension/src/utils/telemetry.ts |
Switches to dangerous-send path, adds manual gating, common properties, and value sanitization; hard-codes telemetry client version. |
extension/src/utils/telemetryRegistry.ts |
Renames event schema keys to final aspire/vscode/... and aspire/dashboard/... wire names; documents the convention. |
extension/telemetry.json |
Updates classification inventory keys to the new wire names. |
extension/src/dcp/DashboardTelemetryPassthrough.ts / AspireDcpServer.ts / AspireDebugSession.ts / AppHostLaunchService.ts / AppHostsViewTelemetry.ts / meaningfulEngagement.ts / appHostDiscovery.ts / workspace.ts / interactionService.ts |
Update event-name call sites and passthrough comments to the new namespaces. |
extension/src/test/*.test.ts, extension/src/test-e2e/packageSurface.e2e.test.ts |
Extensive tests for the attach flow, telemetry gating/sanitization/naming, and the manifest/surface contributions. |
Review details
- Files reviewed: 29/29 changed files
- Comments generated: 3
- Review effort level: Medium
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
I tried to place these as inline tracking comments, but GitHub rejected new inline comments because there is already a pending review draft for this user on the PR. I kept the tracking here instead while the branch was fixed:
These were fixed before the later Windows punctuation redaction follow-up. This is a top-level tracking comment, so there is no review thread to resolve; final tracking is in #18602 (comment). |
|
Pushed Validation on the live head passed locally: Remaining non-agent gates: live CoreCLR attach/manual readiness pass, plus telemetry/reviewer sign-off for the |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
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-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Preserve AppHost process identity, detect Aspire-owned debuggee processes, and publish non-sensitive .NET launch metadata for exact attach target resolution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
| function isAppHostProcessForTarget(process: LaunchedChildProcess, appHostPaths: readonly string[]): boolean { | ||
| return appHostPaths.some(appHostPath => areProcessPathsEqual(process.executable, appHostPath)); |
| resources = await this._dependencies.appHostRepository.fetchAppHostResourcesOnce( | ||
| resolvedTarget.absolutePath, | ||
| request.cancellationToken); |
| */ | ||
| function toContainedPosixRelativePath(folderPath: string, candidate: string): string | undefined { | ||
| const relative = path.relative(folderPath, candidate); | ||
| if (relative.length === 0 || relative.startsWith('..') || path.isAbsolute(relative)) { |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
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-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
There was a problem hiding this comment.
Review details
Suppressed comments (2)
extension/src/debugger/languages/dotnet.ts:764
- On macOS,
ps -o comm=supplies only the executable name, but the current-snapshot path uses the exactTargetPathbranch and this matcher compares that basename with the full apphost path. BecauseUseAppHostis normally true for executable .NET projects, their candidate is never recognized and attach times out on macOS. Also match the program parsed fromprocess.command, which is populated fromps -o args=with the launched path.
function isAppHostProcessForTarget(process: LaunchedChildProcess, appHostPaths: readonly string[]): boolean {
return appHostPaths.some(appHostPath => areProcessPathsEqual(process.executable, appHostPath));
}
extension/src/lm/appHostTargetResolverService.ts:241
- This containment check rejects valid in-workspace paths whose first segment merely starts with
.., such as..cache/AppHost.csproj. Those AppHosts can be discovered and shown by the extension but can never be selected by either language-model tool. Restrict the escape check to the actual parent segment.
function toContainedPosixRelativePath(folderPath: string, candidate: string): string | undefined {
const relative = path.relative(folderPath, candidate);
if (relative.length === 0 || relative.startsWith('..') || path.isAbsolute(relative)) {
return undefined;
- Files reviewed: 63/63 changed files
- Comments generated: 2
- Review effort level: Balanced
| assert.deepStrictEqual(invocation.results, [{ | ||
| tool: resourceDebugToolName, | ||
| success: false, | ||
| outcome: 'debuggerExtensionMissing', | ||
| appHost: relativeAppHostPath, | ||
| resourceName: worker.name, | ||
| requestedStrategy: 'auto', | ||
| effectiveStrategy: 'none', | ||
| controller: 'none', | ||
| debuggerExtensions: [{ id: 'ms-dotnettools.csharp', label: 'C#' }], | ||
| }]); |
|
|
||
| ### Features | ||
|
|
||
| - Add an Attach debugger action for running .NET project resources in the Aspire pane when the C# extension is installed ([#18602](https://github.com/microsoft/aspire/pull/18602)). |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
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-Session: daf7d04c-3dfd-4f43-927d-830d288b9bf7
There was a problem hiding this comment.
Review details
Suppressed comments (3)
extension/src/debugger/resourceDebugService.ts:195
- The selected AppHost PID is dropped during the fresh resource refresh.
aspire describe --apphost <path>cannot distinguish overlapping runs;AppHostConnectionResolver.ResolveConnectionAsynciterates matching sockets and returns the first connection (src/Aspire.Cli/Backchannel/AppHostConnectionResolver.cs:138-155). Thus a tree item for PID 2222 can fetch PID 1111's resource metadata and attach to the wrong process while session tracking records 2222. Carry the resolved PID/socket identity into the describe call, or fetch the snapshot through the exact resolved AppHost connection.
resources = await this._dependencies.appHostRepository.fetchAppHostResourcesOnce(
resolvedTarget.absolutePath,
request.cancellationToken);
extension/src/lm/appHostTargetResolverService.ts:240
- This containment check also rejects legitimate in-workspace paths whose first segment merely begins with
.., such as..services/AppHost.csproj. Check for the parent segment itself (..) or..${path.sep}instead of any..prefix.
if (relative.length === 0 || relative.startsWith('..') || path.isAbsolute(relative)) {
extension/src/test-e2e/resourceDebugTools.e2e.test.ts:142
- The running-resource E2E stops at
debuggerExtensionMissing, so it never exercises process discovery,startDebugging, breakpoint binding, detach, or verifies that the resource survives detach. No committed E2E covers Go attachment either. This leaves the central new behavior unprotected and does not meet issue #18538's explicit .NET and non-.NET attach/breakpoint/detach completion criterion; add packaged-VSIX scenarios with the debugger extensions installed for both providers.
- Files reviewed: 63/63 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Summary
Adds resource debugger attachment for running .NET and Go resources from the Aspire view and the
aspire_resource_debugCopilot tool.Fixes #18538.
Fixes #18937.
What changed
dotnet run,dotnet watch, orgo run. Process discovery supports Windows, macOS, and Linux.dotnet runrequires the direct application child, whiledotnet watchandgo runsupport verified descendants.alreadyDebugging.project.launchCommand,project.configuration, andproject.targetFrameworksnapshot metadata. Current snapshots use exactTargetPathidentity; theTargetNamecompatibility fallback is limited to older snapshots that do not contain the new metadata.aspire_resource_debugwith input validation, explicit confirmation, invocation-time re-resolution, linked cancellation, and bounded output.Evidence
Real process-tree validation covers:
dotnet run: the application is a direct child.dotnet watch: the application is a transitive descendant.go run: the application is a descendant.Packaged VSIX E2E exercises registration, preparation, confirmation, cancellation, stopped/unsupported/missing-resource outcomes, and bounded output through the real Extension Host.
This PR does not claim committed end-to-end breakpoint coverage for every supported language.
Validation
Checklist
The language-model boundary intentionally excludes process IDs, absolute paths, arguments, environment values, and raw failures. Debug attachment also requires VS Code confirmation before invocation.