Skip to content

Refactor serverReadyAction handling#13930

Open
dkattan wants to merge 13 commits intodotnet:mainfrom
dkattan:dkattan/server-ready-action-parent
Open

Refactor serverReadyAction handling#13930
dkattan wants to merge 13 commits intodotnet:mainfrom
dkattan:dkattan/server-ready-action-parent

Conversation

@dkattan
Copy link
Contributor

@dkattan dkattan commented Jan 15, 2026

Motivation

Aspire 13 improved debugging for child processes; this change extends that all the way to the browser.

Expectation

If I use the following configuration in launch.json

{
      "name": "Aspire",
      "type": "aspire",
      "request": "launch",
      "program": "${workspaceFolder}/Aspire/Immybot.AppHost/Immybot.AppHost.csproj",
      "serverReadyAction": {
        "action": "debugWithEdge",
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)",
        "killOnServerStop": true,
      }
    }

Then Edge will start with the debugger attached and navigate to http://localhost:50061 when my application emits Now listening on: http://localhost:50061

Actual behavior

  1. The browser doesn't open at all because my launch profile in launchSettings.json did not have applicationUrl set
  2. applicationUrl doesn't support tokens/interpolation so I can't have it automatically launch to the port DCP chose for the frontend

Root causes

  1. Hard-coded serverReadyAction
  2. Dependence on applicationUrl from launchSettings.json

Hard-coded serverReadyAction

User supplied serverReadyAction on the Aspire launch config is being overwritten by an over-simplistic hard-coded default in launchProfiles.ts

let uriFormat = applicationUrl.includes(';') ? applicationUrl.split(';')[0] : applicationUrl;

    return {
        action: "openExternally",
        pattern: "\\bNow listening on:\\s+https?://\\S+",
        uriFormat: uriFormat
    };

That prevented browser breakpoint debugging and custom URL capture patterns. In this PR we honor the supplied serverReadyAction, so browser debugging can be triggered correctly (e.g., debugWithChrome / debugWithEdge) with a custom pattern and uriFormat.

Dependence on applicationUrl from launchSettings.json

In my scenario, Aspire is dynamically assigning ports to the frontend (so I can have multiple worktrees going for maximum vibing) but applicationUrl from launchSettings.json is not expanded to support any sort of tokens/interpolation from env vars and must have valid URI syntax (aside from splitting on ;).

We could simply adjust pattern to actually capture the emitted URL and use uriFormat: "%s" instead, which would be better but you'd still be hardcoding other properties like action: "openExternally" and not honoring the user's specifications.

Changes

  • Add a typed ServerReadyAction to DCP types and expose it on AspireExtendedDebugConfiguration.
  • Note that VS Code does not export ServerReadyAction and link to its canonical definition.
  • Centralize serverReadyAction inheritance/selection inside determineServerReadyAction.
  • Pass parentDebugConfiguration (the user's supplied launch configuration) into determineServerReadyAction from the .NET debugger and use it if available and only fall-back to the existing hard-coded one if it doesn't exist.
  • Update tests for the new behavior and URL-capture pattern.

Tests

  • launchProfiles.test.ts
  • dotnetDebugger.test.ts

@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 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/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 13930

Or

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

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 15, 2026
@dkattan
Copy link
Contributor Author

dkattan commented Jan 15, 2026

@dkattan please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@dotnet-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@dotnet-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@dotnet-policy-service agree company="Microsoft"

Contributor License Agreement

@dotnet-policy-service agree

@dkattan dkattan marked this pull request as ready for review January 15, 2026 15:16
Copilot AI review requested due to automatic review settings January 15, 2026 15:17
Copy link
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

This PR refactors the serverReadyAction handling in the VS Code extension to honor user-specified server ready actions from the Aspire launch configuration, enabling browser debugger attachment (e.g., debugWithChrome, debugWithEdge) and custom URL patterns. Previously, a hard-coded default configuration would overwrite any user-supplied settings.

Changes:

  • Added ServerReadyAction type definition and exposed it on AspireExtendedDebugConfiguration
  • Refactored determineServerReadyAction to accept options and inherit from parent debug configuration
  • Updated pattern to use capture groups ((https?://\\S+)) for proper URL substitution with %s format

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
extension/src/dcp/types.ts Added ServerReadyAction type and parentDebugConfiguration field to LaunchOptions and AspireExtendedDebugConfiguration
extension/src/debugger/launchProfiles.ts Refactored determineServerReadyAction to accept options object and support parent configuration inheritance
extension/src/debugger/languages/dotnet.ts Updated call to determineServerReadyAction to pass parentDebugConfiguration from launchOptions
extension/src/test/launchProfiles.test.ts Updated tests to use new API and added cross-platform path handling
extension/src/test/dotnetDebugger.test.ts Updated assertions to match new serverReadyAction behavior with capture groups

dkattan and others added 4 commits January 15, 2026 09:22
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

export function determineServerReadyAction(
options: ServerReadyActionOptions = {}
): ServerReadyAction | undefined {
if (!options.launchBrowser) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we actually gate this on launchSettings.json's launchBrowser property?

launchSettings.json (IMO) is a relic from Visual Studio

launch.json (IMO) is the "VS Code" way

I know that's an over-simplification and that we are conceptually layering them so launch.json -> launchSettings.json -> target project

I guess my point is it took me a while to realize that both applicationUrl and launchBrowser both needed to be specified before I could have the privilege of using the hardcoded serverReadyAction.

I don't want other VS Code purists to get frustrated that their serverReadyAction isn't working because a setting for Visual Studio isn't specified.

Copy link
Member

Choose a reason for hiding this comment

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

It seems reasonable to not need launchBrowser specified if there is not a launchSettings.json input. I'll make the change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You know, I thought about this some more and it occurred to me that in the absence of launchBrowser: true, we may end up attaching a serverReadyAction to multiple projects that don't need it. My understanding is that the serverReadyAction uses an expensive call to vscode.window.onDidWriteTerminalData so we might actually want to keep this and document it.

Copy link
Member

Choose a reason for hiding this comment

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

I like the idea that specifying serverReadyAction on the entire debug session will apply to all started resources in the application.

We are going to expose a hook to apply debugger properties to specific resources, if that's the behavior you're looking for - #12711

debugSessionId: string;
isApphost: boolean;
debugSession: AspireDebugSession;
parentDebugConfiguration?: AspireExtendedDebugConfiguration;
Copy link
Member

Choose a reason for hiding this comment

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

Strangely, this added line in the diff does not show up in the actual branch code.

): LaunchProfileResult {
const debugMessage =
`[launchProfile] determineBaseLaunchProfile:
disable_launch_profile=${launchConfig.disable_launch_profile === true}
Copy link
Member

Choose a reason for hiding this comment

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

nit: !! instead of true equality check

export function determineServerReadyAction(
options: ServerReadyActionOptions = {}
): ServerReadyAction | undefined {
if (!options.launchBrowser) {
Copy link
Member

Choose a reason for hiding this comment

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

It seems reasonable to not need launchBrowser specified if there is not a launchSettings.json input. I'll make the change

@adamint
Copy link
Member

adamint commented Jan 16, 2026

Ok, I've wrapped my head around the problem here. The solution presented seems incomplete (ie, launchOptions.parentDebugConfiguration is never set) so doesn't fix the issue at hand, which is that server ready actions cannot be provided on the aspire launch configuration itself.

Short term, to provide a workaround, if it is then we should override any existing server ready action. Longer term, overlaps with #12711 as the explicit launch.json serverReadyAction should also replace any serverReadyAction provided in a resource's debugger_properties.

@adamint
Copy link
Member

adamint commented Jan 16, 2026

@dkattan how do my changes look to you?

@adamint
Copy link
Member

adamint commented Jan 16, 2026

I've been thinking more about this, and would like to move these changes into #12711, closing this PR for now.

@dkattan
Copy link
Contributor Author

dkattan commented Jan 16, 2026

I've been thinking more about this, and would like to move these changes into #12711, closing this PR for now.

I very much like the idea of configuring the debugger on each resource in the AppHost. That fixes some of the behavioral ambiguity. I'll focus my efforts on #12711

@dkattan dkattan closed this Jan 16, 2026
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Jan 16, 2026
@dkattan
Copy link
Contributor Author

dkattan commented Jan 23, 2026

I'm reopening this because the changes here work with and don't seem to overlap with #12711 and it would at least unstick me and allow debugging without waiting for that other PR @adamint @davidfowl

@dkattan dkattan reopened this Jan 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants