Skip to content

Add support for "launchUrl" in launchSettings.json#17634

Merged
adamint merged 5 commits into
microsoft:mainfrom
neoGeneva:launch-profile-launch-url-support
May 30, 2026
Merged

Add support for "launchUrl" in launchSettings.json#17634
adamint merged 5 commits into
microsoft:mainfrom
neoGeneva:launch-profile-launch-url-support

Conversation

@neoGeneva
Copy link
Copy Markdown
Contributor

Description

Use launchUrl as the value set for serverReadyAction.uriFormat if it's set in launchSettings.json when launching the debugger.

Fixes #17633

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Copilot AI review requested due to automatic review settings May 28, 2026 22:56
@neoGeneva neoGeneva requested a review from adamint as a code owner May 28, 2026 22:56
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

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for a separate browser launch URL (launchUrl) in .NET launch profiles so the debugger can open a specific page instead of the base applicationUrl.

Changes:

  • Extend LaunchProfile with optional launchUrl.
  • Use launchUrl (fallback to applicationUrl) when constructing serverReadyAction.
  • Update launch profile tests to validate parsing/propagation of launchUrl.

Reviewed changes

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

File Description
extension/src/test/launchProfiles.test.ts Adds a launchUrl field to the test launchSettings input and asserts it is parsed into the profile.
extension/src/debugger/launchProfiles.ts Extends the LaunchProfile interface with the new launchUrl property and documentation.
extension/src/debugger/languages/dotnet.ts Prefer launchUrl over applicationUrl when configuring serverReadyAction browser opening behavior.

Comment thread extension/src/debugger/launchProfiles.ts Outdated
Comment thread extension/src/debugger/languages/dotnet.ts Outdated
Comment thread extension/src/test/launchProfiles.test.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 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 -- 17634

Or

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

@neoGeneva
Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

Copy link
Copy Markdown
Member

@adamint adamint left a comment

Choose a reason for hiding this comment

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

Reviewed by three parallel agents focused on URL-parsing correctness, call-site fit, and contract/UX. The core direction is right and consistent with dotnet run / Visual Studio semantics, and the bug-fix path works for the issue's headline case (absolute launchUrl with wildcard applicationUrl). However, the unguarded new URL() introduces a real regression for some previously-tolerated applicationUrl values, the absolute-launchUrl override silently broadens the URI-scheme contract that VS Code's openExternally will hand to the OS, and the new tests don't actually exercise the bug from issue #17633 (a no-op implementation would pass them).

Blockers / High

  1. new URL(launchUrl, uriFormat) throws ERR_INVALID_URL for previously-tolerated applicationUrl values, aborting the entire debug session. Pre-PR, an unparseable / empty / leading-semicolon applicationUrl was silently passed through as serverReadyAction.uriFormat — VS Code's serverReadyAction would just fail to open a browser. Post-PR, when both an invalid applicationUrl and a launchUrl are present, new URL(...) throws. There is no try/catch; the exception propagates through extension/src/debugger/languages/dotnet.ts:312 → the awaited createDebugSessionConfigurationCallbackextension/src/dcp/AspireDcpServer.ts:173, which responds to DCP with HTTP 500 and the resource fails to start. Reproducible cases verified with Node 24: ";https://localhost:5001" + "/abc", " " + "/abc", "not-a-url" + "/abc" — all throw. Critically, an absolute launchUrl does NOT save the user: WHATWG new URL() requires the base to be parseable whenever a base is supplied, regardless of whether the first arg is absolute (new URL('https://abs.com', 'malformed') throws). (Inline comment.)

Medium

  1. No scheme validation on launchUrl — arbitrary URI schemes are passed to vscode.env.openExternal. Because launchUrl is resolved with new URL(launchUrl, base), any absolute launchUrl completely overrides the http(s) base and is returned verbatim. Verified: new URL('javascript:alert(1)', 'https://localhost:5001').href === 'javascript:alert(1)'; same for data:, file:, vscode-insiders://command/.... The result becomes serverReadyAction.uriFormat, which VS Code feeds to openExternal when the debug-console pattern matches. The extension's other URL-opening helpers (AspireAppHostTreeProvider.openInExternalBrowser, EndpointUrlItem) all funnel through isLinkableUrl in extension/src/utils/urlSchemes.ts; the new launchUrl code path bypasses that. launchSettings.json is normally trusted, so this is not critical — but it silently broadens the contract from "open an http(s) URL" to "open any URI scheme" and is inconsistent with the rest of the extension. The regex pattern already constrains matches to https?://, so non-http(s) uriFormat is semantically inconsistent with it anyway. (Inline comment.)

  2. The added tests do not actually exercise the bug from issue #17633. Both new tests use applicationUrl = 'https://localhost:5001;http://localhost:5000' with launchUrls like 'https://localhost:5001/some/path' or '/some/path' — i.e. the launchUrl either equals or resolves cleanly against the first applicationUrl entry. A no-op implementation that returned applicationUrl.split(';')[0] followed by simple concatenation would pass them. The issue's motivating scenario is applicationUrl: "http://*:80/;https://*:443/" + launchUrl: "https://mywebsite.localhost"; verified that the fix works for it (new URL('https://mywebsite.localhost', 'http://*:80/').href === 'https://mywebsite.localhost/'), but that exact scenario should be pinned in a test so a future refactor (or finding #1's fallback logic) can't silently re-break it. (Inline comment on the absolute-launchUrl test.)

Low

  1. Interface comment says "relative" but the implementation handles both. // The relative URL to launch in the browser. is copied verbatim from the SchemaStore description, but the function explicitly accepts both relative and absolute launchUrl values — and the issue #17633 fix relies on the absolute case. A future maintainer reading only the interface comment would be justified in adding an if (looksRelative(launchUrl)) guard and re-breaking the issue. One-line addition like // May be absolute (e.g. "https://my.localhost"); when relative, it is resolved against the first applicationUrl entry. resolves this without re-litigating the schemastore-wording debate. (Inline comment.)

  2. No call-site integration test in extension/src/test/dotnetDebugger.test.ts. The existing integration test "applies launch profile settings to debug configuration" (~line 514) verifies serverReadyAction.uriFormat is populated from applicationUrl, but no analogous assertion was added that launchUrl from the profile is plumbed through dotnet.ts:312 into serverReadyAction.uriFormat. The new unit tests exercise the helper in isolation; if a future refactor of dotnet.ts drops baseProfile?.launchUrl from the third positional argument (easy mistake with three optional positional args), no test will fail.

  3. Test gap: relative launchUrl with the issue's wildcard applicationUrl. The "relative launchUrl" test uses a "nice" https://localhost:5001 base. For the issue's actual 'http://*:80/' base + a relative launchUrl: '/dashboard', the implementation produces http://*/dashboard (port stripped because * parses as host and 80 is the default for http:). Verified with Node. That URL is not openable by any browser, so users hitting the issue with a relative launchUrl still see broken behavior post-PR. The PR doesn't necessarily have to fix this case, but the test for "relative launchUrl" gives a false impression of completeness; either document the limitation or skip resolution when the base host is unresolvable.


Verified safe (not flagged)

  • Parity with dotnet run / Visual Studio: using applicationUrl.split(';')[0] as the base matches both dotnet run and Visual Studio's behavior of taking the first applicationUrl. Also matches Aspire's own ProjectResourceBuilderExtensions absolute-vs-relative semantics.
  • Apphost branch is correctly excluded: the dashboard URL is opened separately via AspireDebugSession.openDashboard from DCP-reported URLs, not from launchSettings — so the if (!launchOptions.isApphost) guard is intentional and launchUrl on an AppHost's own launchSettings.json is silently ignored, consistent with that flow.
  • Only one production call site: confirmed by grep, dotnet.ts:312 is the sole non-test caller of determineServerReadyAction. No other language debuggers (python.ts, go.ts, node.ts, azureFunctions.ts, browser.ts, cli.ts) reference serverReadyAction, launchBrowser, applicationUrl, or launchUrl — serverReadyAction is .NET-specific in this codebase (relies on ASP.NET Core's \bNow listening on: log pattern), so issue #17633 has no cross-language analog.
  • No other consumer of LaunchProfile.applicationUrl opens a browser at a wrong URL: the "Open in external/integrated browser" actions on EndpointUrlItem use DCP-reported resource URLs, not launch-profile applicationUrl. The user-visible bug from #17633 has no second surface that this PR silently leaves broken.
  • Signature change is wire-compatible: adding launchUrl?: string as a third optional positional parameter is non-breaking; existing callers type-check under strict: true. (Mild design smell — three optional positional args is awkward — but no concrete bug; an options-object refactor would be a reasonable future improvement.)
  • yarn compile-tests and yarn lint pass cleanly with no warnings against PR changes.
  • WHATWG URL edge cases verified safe (with Node 24): query strings, fragments, protocol-relative URLs, IPv6 wildcard bases (http://[::]:5000), 0.0.0.0 bases, whitespace launchUrl, and empty-string launchUrl all behave as expected (no crash, sensible result).
  • Env-var expansion in launchUrl: not expanded by VS or dotnet run; not expanding here is correct.
  • Aspire runtime port rebinding vs. static uriFormat: pre-existing limitation, not made worse by this PR.
  • VS Code serverReadyAction.uriFormat %s substitution: not used pre-PR either; behavior preserved.
  • Test wiring: extension/.vscode-test.mjs picks up new suite/test blocks in launchProfiles.test.ts; they will run in CI.
  • SchemaStore type fidelity: launchUrl is "type": "string" with no further constraints; the LaunchProfile.launchUrl?: string typing is faithful.

Comment thread extension/src/debugger/launchProfiles.ts Outdated
Comment thread extension/src/debugger/launchProfiles.ts
Comment thread extension/src/test/launchProfiles.test.ts
Comment thread extension/src/debugger/launchProfiles.ts Outdated
@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.

adamint and others added 3 commits May 30, 2026 15:28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

Copy link
Copy Markdown
Member

@adamint adamint left a comment

Choose a reason for hiding this comment

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

Final review: changes look good. CI is green after rerun.

@adamint adamint merged commit 9b74c9a into microsoft:main May 30, 2026
618 of 621 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.5 milestone May 30, 2026
@neoGeneva
Copy link
Copy Markdown
Contributor Author

Thanks @adamint!

@neoGeneva
Copy link
Copy Markdown
Contributor Author

Hey @adamint, out of curiosity what did you use to generate the "Reviewed by three parallel agents focused on URL-parsing correctness, call-site fit, and contract/UX." comment above? It's much more detailed than what copilot does.

@adamint
Copy link
Copy Markdown
Member

adamint commented May 31, 2026

Thanks @adamint!

Thanks for contributing! This will be available for use by Tuesday.

@adamint
Copy link
Copy Markdown
Member

adamint commented May 31, 2026

Hey @adamint, out of curiosity what did you use to generate the "Reviewed by three parallel agents focused on URL-parsing correctness, call-site fit, and contract/UX." comment above? It's much more detailed than what copilot does.

It's largely relying on the code-review skill in our repo. For an automated review, I prompt copilot to review with a list of models and that skill and mention that particular attention should be paid to specific areas depending on the PR.

It's a relatively new workflow for me and works really well! Certainly uses a lot of tokens though...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support "launchUrl" in launchSettings.json in VSCode extension

3 participants