Skip to content

Fix aspire wait for JSON-emitted resource names#16224

Merged
JamesNK merged 23 commits intomainfrom
sebastienros/fix-aspire-wait-resource-name
Apr 20, 2026
Merged

Fix aspire wait for JSON-emitted resource names#16224
JamesNK merged 23 commits intomainfrom
sebastienros/fix-aspire-wait-resource-name

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

Description

aspire wait was forwarding the user-supplied resource token directly to the AppHost wait RPC. That RPC only accepts the canonical resource Name, which meant the generated name emitted by aspire ps --format json --resources could fail even though the friendly display name worked.

This updates wait to resolve the input against current resource snapshots before invoking the backchannel. Exact canonical names still pass through, unique display names are mapped to the canonical Name, and ambiguous replica display names still fail instead of guessing. The PR also updates the wait help text to say it accepts a resource name or identifier and adds unit coverage for the resolution behavior.

Fixes #15842

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?

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 16, 2026 00:02
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 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 -- 16224

Or

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

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

This PR fixes aspire wait so it accepts both canonical resource Name values (as emitted by aspire ps --format json --resources) and unique displayName values, by resolving the user input against the current AppHost resource snapshots before invoking the wait RPC.

Changes:

  • Resolve the user-supplied resource token to a canonical resource Name using resource snapshots before calling the AppHost wait RPC.
  • Update wait argument help text to indicate it accepts a resource name or identifier.
  • Add unit tests covering display-name resolution, canonical-name pass-through, and ambiguous display-name failure.

Reviewed changes

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

Show a summary per file
File Description
src/Aspire.Cli/Commands/WaitCommand.cs Adds snapshot-based resolution from user input to canonical resource name before waiting.
src/Aspire.Cli/Resources/WaitCommandStrings.resx Updates the resource argument description string to “name or identifier”.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.cs.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.de.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.es.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.fr.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.it.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.ja.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.ko.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.pl.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.pt-BR.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.ru.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.tr.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.zh-Hans.xlf Updates localized string source/target for the resource argument description.
src/Aspire.Cli/Resources/xlf/WaitCommandStrings.zh-Hant.xlf Updates localized string source/target for the resource argument description.
tests/Aspire.Cli.Tests/TestServices/TestAppHostAuxiliaryBackchannel.cs Captures the last wait call inputs for assertions in unit tests.
tests/Aspire.Cli.Tests/Commands/WaitCommandTests.cs Adds unit tests for display-name resolution, canonical-name pass-through, and ambiguous-name failure.

Comment thread src/Aspire.Cli/Commands/WaitCommand.cs Outdated
Comment thread tests/Aspire.Cli.Tests/Commands/WaitCommandTests.cs Outdated
sebastienros and others added 2 commits April 15, 2026 17:14
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sebastienros and others added 3 commits April 15, 2026 17:48
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

This client-side resolution in WaitCommand is inconsistent with how ResourceCommand handles the same scenario. ResourceCommand passes the raw user-supplied name to ExecuteResourceCommandAsync, which flows to ResourceCommandService.ExecuteCommandAsyncResourceNotificationService.TryGetCurrentState, where the server resolves display names to canonical names (exact match first, then fallback to Resource.Name when unambiguous).

WaitForResourceAsync on the server side doesn't use TryGetCurrentState — it matches against appModel.Resources using r.Name directly, which is why it needed a fix. But arguably the more consistent fix would be to add the same TryGetCurrentState-style resolution to the server-side WaitForResourceAsync in AuxiliaryBackchannelRpcTarget, rather than adding client-side resolution in the CLI. That would fix it for all callers (not just the CLI) and keep the resolution responsibility in one place.

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

I think the wait command should solve this problem the same way the resource command does. The partial resource name should be resolved inside the app host

@sebastienros
Copy link
Copy Markdown
Contributor Author

This client-side resolution in WaitCommand is inconsistent with how ResourceCommand handles the same scenario. ResourceCommand passes the raw user-supplied name to ExecuteResourceCommandAsync, which flows to ResourceCommandService.ExecuteCommandAsync -> ResourceNotificationService.TryGetCurrentState, where the server resolves display names to canonical names (exact match first, then fallback to Resource.Name when unambiguous).

WaitForResourceAsync on the server side doesn't use TryGetCurrentState - it matches against appModel.Resources using r.Name directly, which is why it needed a fix. But arguably the more consistent fix would be to add the same TryGetCurrentState-style resolution to the server-side WaitForResourceAsync in AuxiliaryBackchannelRpcTarget, rather than adding client-side resolution in the CLI. That would fix it for all callers (not just the CLI) and keep the resolution responsibility in one place.

Addressed in 1378456ab. The backchannel wait path now resolves on the AppHost side by matching both the unique resource id and the logical resource name, and it no longer fails early before the AppHost has published its resources. That keeps the resolution responsibility in one place and fixes the startup race that was still reproducing with the CLI-only approach.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

But I still see resource name resolution logic in WaitCommand...

Comment thread src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros
Copy link
Copy Markdown
Contributor Author

But I still see resource name resolution logic in WaitCommand...

Fixed in 68c132edb. WaitCommand now forwards the raw user input again. The AppHost backchannel resolves the identifier instead: it uses the current resource state when available, falls back to the app model to map a unique logical resource name to its resolved instance id, and still accepts exact resolved ids. That keeps the resolution behavior in the AppHost and preserves the startup-race fix without keeping snapshot-based resolution in the CLI.

sebastienros and others added 4 commits April 15, 2026 20:30
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>
@sebastienros
Copy link
Copy Markdown
Contributor Author

Everything is moved to the AuxiliaryBackchannelRpcTarget and uses TryGetCurrentState before checking the app model when there are no snapshots yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs Outdated
Comment thread src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs
Comment thread tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelRpcTargetTests.cs Outdated
Comment thread tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelRpcTargetTests.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.

1 issue flagged: misleading error message when resource name is ambiguous (multiple replicas) vs. genuinely not found.

Comment thread src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs
sebastienros and others added 7 commits April 17, 2026 08:02
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>
@github-actions
Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 72 recordings uploaded (commit 80867fd)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View Recording
DeployK8sBasicApiService ▶️ View Recording
DeployK8sWithGarnet ▶️ View Recording
DeployK8sWithMongoDB ▶️ View Recording
DeployK8sWithMySql ▶️ View Recording
DeployK8sWithPostgres ▶️ View Recording
DeployK8sWithRabbitMQ ▶️ View Recording
DeployK8sWithRedis ▶️ View Recording
DeployK8sWithSqlServer ▶️ View Recording
DeployK8sWithValkey ▶️ View Recording
DeployTypeScriptAppToKubernetes ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance ▶️ View Recording
DoListStepsShowsPipelineSteps ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View Recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24573554565

@davidfowl
Copy link
Copy Markdown
Contributor

Did you test this with an older apphost?

@sebastienros
Copy link
Copy Markdown
Contributor Author

@davidfowl do you mean we should see if older apphosts had the issue? If so how old? We didn't have json results that long ago.

@davidfowl
Copy link
Copy Markdown
Contributor

new cli old apphost is what needs to always work.

@sebastienros
Copy link
Copy Markdown
Contributor Author

@davidfowl

1- we are not touching the protocol definition, WaitForResourceAsync(WaitForResourceRequest) : WaitForResourceResponse is unchanged
2- old apphost with new cli will still work, but the issue will still hapen

});

var response = await waitTask.WaitAsync(TimeSpan.FromSeconds(10));
var response = await waitTask.WaitAsync(TestConstants.DefaultTimeoutTimeSpan);
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.

No, use DefaultTimeout in tests. A shared file may need to be added to the project. It adapts waiting time based on the environment, no ops when debugger is attached, provides better error message.

@JamesNK JamesNK merged commit f285799 into main Apr 20, 2026
559 of 563 checks passed
@JamesNK JamesNK deleted the sebastienros/fix-aspire-wait-resource-name branch April 20, 2026 04:18
@github-actions github-actions Bot added this to the 13.3 milestone Apr 20, 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.

aspire wait does not accept the resource name emitted by JSON output

4 participants