fix(cli): restore implicit-channel discovery + guard non-interactive fuzzy auto-pick (#17724, #17725)#17728
Conversation
…fuzzy auto-pick (#17724, #17725) ## #17725 — Prerelease-only integrations invisible on polyglot apphosts `IntegrationPackageSearchService.GetIntegrationPackagesWithChannelsAsync` used to narrow the channel set to whatever `configuredChannel` resolved to (from `aspire.config.json`'s `"channel"` field). For a polyglot apphost pinned to a `Quality.Stable` channel this dropped the implicit channel from discovery, so prerelease-only packages (e.g. `Aspire.Hosting.Foundry`, `Aspire.Hosting.Kubernetes`) became invisible. This narrowing was born 2026-01-13 in PR #13705 with a C#-only short-circuit in `GetConfiguredChannel`. It stayed dormant until PR #17452 (2026-05-26) started writing `"channel": "<identity>"` into the scaffolded `aspire.config.json` during `aspire init`. After #17452 every newly-init'd polyglot apphost in 13.4 had the field populated and tripped the narrowing. 13.3.5 users had no `"channel"` persisted, so the bug was invisible there — this is a 13.4 regression introduced by the activator, not the narrowing code itself. The fix removes the narrowing. The configured channel is still forwarded to `PackagingService.GetChannelsAsync` as `requestedChannelName` so out-of-tree apphost staging-channel synthesis keeps working — it just no longer constrains the post-retrieval filter pipeline. The filter pipeline is now byte-identical for C# and polyglot apphosts. ## #17724 — `aspire add <fuzzy> --non-interactive` silently picks first match `AddCommand` falls back to fuzzy search when there's no exact match. The fuzzy candidates were passed to `GetPackageByInteractiveFlow`, which in non-interactive mode auto-selected `distinctPackages.First()` and silently installed it. Combined with #17725, `aspire add kube --non-interactive` on a TS apphost silently installed `Aspire.Hosting.Azure`. The existing guard at AddCommand.cs:181 already refused this when `--version` was supplied. This change generalizes the guard: any non-interactive invocation without an exact match now fails with a new `NonInteractiveRequiresExactPackageMatch` resource message. Fuzzy fallback remains available in interactive mode. ## Tests - IntegrationSearchCommandFormatJsonWithTypeScriptAppHostPinnedToChannelAlsoSearchesImplicitChannel - IntegrationSearchCommandFormatJsonWithTypeScriptAppHostPinnedToStagingChannelAlsoSearchesImplicitChannel - IntegrationSearchCommandFormatJsonWithTypeScriptAppHostPinnedToStableChannelStillSurfacesPrereleaseOnlyPackages (primary regression test for #17725 — Foundry case) - IntegrationSearchCommandTypeScriptAppHostProducesSameResultRegardlessOfPersistedChannel (durable structural guard: parameterized over with/without `"channel"` in aspire.config.json; asserts the result is identical, proving the narrowing is gone) - AddCommand_NonInteractive_NoExactMatchWithoutVersion_FailsInsteadOfFuzzyAutoPick_Regression17724 (primary regression test for #17724) - AddCommand_NonInteractive_ExactMatchWithoutVersion_StillSucceeds (companion guard: exact-match happy path keeps working non-interactive) Two pre-existing AddCommandFuzzySearchTests were testing the buggy auto-pick behavior implicitly (they used `add postgre` / typo input under the default non-interactive test host). Updated to opt into an interactive host environment to assert the documented interactive-fuzzy-prompt behavior. Full Aspire.Cli.Tests suite: 3900 passed, 21 skipped, 0 failed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17728Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17728" |
There was a problem hiding this comment.
Pull request overview
Fixes two CLI regressions affecting polyglot AppHosts: (1) integration/package discovery incorrectly constrained by persisted channel, and (2) aspire add in non-interactive mode silently picking a fuzzy match instead of failing.
Changes:
- Adjust integration search channel selection to avoid incorrectly narrowing results for polyglot AppHosts.
- Add a non-interactive guard in
aspire addrequiring an exact match (no fuzzy auto-pick). - Add/adjust CLI tests and introduce a new localized error string (RESX + XLF updates).
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Cli.Tests/Commands/AddCommandTests.cs | Adds/updates regression coverage for channel discovery parity and non-interactive add exact-match enforcement. |
| src/Aspire.Cli/Commands/IntegrationPackageSearchService.cs | Changes how channels are selected for integration discovery. |
| src/Aspire.Cli/Commands/AddCommand.cs | Blocks fuzzy fallback in non-interactive mode when there is no exact match; surfaces new error string. |
| src/Aspire.Cli/Resources/AddCommandStrings.resx | Adds NonInteractiveRequiresExactPackageMatch string. |
| src/Aspire.Cli/Resources/AddCommandStrings.Designer.cs | Adds strongly-typed accessor for the new string. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.cs.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.de.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.es.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.fr.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.it.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.ja.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.ko.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.pl.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.pt-BR.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.ru.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.tr.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.zh-Hans.xlf | Adds new trans-unit for the non-interactive exact-match error. |
| src/Aspire.Cli/Resources/xlf/AddCommandStrings.zh-Hant.xlf | Adds new trans-unit for the non-interactive exact-match error. |
Copilot's findings
Files not reviewed (1)
- src/Aspire.Cli/Resources/AddCommandStrings.Designer.cs: Language not supported
- Files reviewed: 17/18 changed files
- Comments generated: 2
…ns a channel Address review feedback on PR #17728. The first revision dropped too much. Removing the `|| !string.IsNullOrEmpty(configuredChannel)` half of the gate caused a NEW regression: a TS apphost pinned to "daily" / "staging" / a custom channel now searched only the implicit channel, losing access to packages that live on the pinned feed. Production change in IntegrationPackageSearchService: channels = hasHives || !string.IsNullOrEmpty(configuredChannel) ? allChannels : allChannels.Where(c => c.Type is PackageChannelType.Implicit); This preserves the #17725 fix (narrowing is still gone, so the implicit channel always participates and prerelease-only packages like Foundry remain discoverable when pinned to a Stable-quality channel) while keeping pinned explicit channels in the search. Tests strengthened with per-channel invocation counters so that "channel X was searched" is asserted directly rather than inferred from the dedupe outcome. The Theory test is reframed: both arms agree on the user-visible preferred result (Redis 1.0.0, implicit wins), but the with-channel arm additionally hits the daily channel and the without-channel arm does not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds proof-by-test that the IPSS gate behaves identically when the CLI
is shipped as staging (`IdentityChannel == "staging"`) as it does today
with the PR dogfood build (where the channel name was `pr-17728`):
1. Adds `[InlineData("\"staging\"", true)]` to the existing theory
`…PersistedChannelExpandsDiscoveryWithoutChangingPreferredResult`.
This proves the IPSS gate (`hasHives || !string.IsNullOrEmpty(
configuredChannel)`) is channel-name-opaque — `"staging"` and
`"daily"` produce identical gate behavior.
2. Adds a new fact
`IntegrationSearchCommandStagingStampedCliWithPinnedStagingApphost
QueriesBothImplicitAndStagingChannelsAndSurfacesPrereleaseOnlyPackages`
that exercises the exact shipping shape:
* Real PackagingService (not the fake TestPackagingService) — so
the real staging-channel synthesis path is exercised.
* `IdentityChannel = Staging` — the CLI binary is stamped as the
staging release identity, which is how shipped staging CLIs run.
* `aspire.config.json` pins `"channel": "staging"` — which is what
`aspire new` writes into polyglot apphosts on a staging-stamped
CLI (see CliTemplateFactory.TypeScriptStarterTemplate).
* No PR hives — this is a real installed CLI, not a dogfood build.
Asserts both invariants:
(i) Total cache call count >= 2, proving both implicit AND staging
channels were queried. Pre-fix narrowing would have produced
exactly 1 call.
(ii) A prerelease-only package returned only when prerelease=true
surfaces to the user — proving the #17725 fix holds on a real
staging release, not just on a PR build.
Together with the existing
`…UsesConfiguredStagingChannelWithRealPackagingService` (apphost-pin
triggers staging synthesis under Stable identity) and
`…UnpinnedAppHostUsesImplicitChannelUnderStagingCli` (staging identity
without pin correctly falls back to implicit-only), the staging
quadrant is now fully covered.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
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.
|
Staging-equivalence proof tests — green ✅Pushed Why these tests prove the staging-stamped CLI will behave identically
What was added
ResultsStaging quadrant coverage (now complete)
|
| // (https://github.com/microsoft/aspire/issues/17724). Refusing with an actionable error | ||
| // forces the caller to supply an exact package id or friendly name. | ||
| if (filteredPackagesWithShortName.Count == 0 && integrationName is not null && !_hostEnvironment.SupportsInteractiveInput) | ||
| { |
There was a problem hiding this comment.
This still leaves the interactive repro unfixed. aspire add kube (without --non-interactive) follows the fuzzy fallback below, and if that returns a single candidate, GetPackageByInteractiveFlow auto-selects it via distinctPackages.Length == 1 instead of prompting. #17724 was observed in interactive mode too, so this guard needs to apply to fuzzy matches generally, or the single fuzzy-candidate case needs to force an explicit prompt/confirmation instead of silently installing the first match.
Prompt before adding a single fuzzy or no-match fallback candidate in interactive aspire add flows, while preserving exact-match auto-selection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
❓ CLI E2E Tests unknown — 110 passed, 0 failed, 2 unknown (commit View all recordings
📹 Recordings uploaded automatically from CI run #26687745746 |
…nd interactive confirmation behavior Documents the behavior changes from microsoft/aspire#17728: - Non-interactive mode now requires an exact friendly name or package ID - Interactive mode now prompts before adding fuzzy/no-match fallback candidates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Pull request created: #1139
|
|
📝 Documentation has been drafted in microsoft/aspire.dev#1139 targeting Updated
Note This draft PR needs human review before merging. |
Fixes #17724. Fixes #17725.
What changed
This PR addresses two release/13.4 blockers that share a root cause.
Root cause — how we got here (three layers)
The user-facing symptom is "13.4 hides Kubernetes / Foundry on
aspire add, 13.3.5 didn't". That's a regression. But the code responsible for hiding them is identical in both versions — what changed is what populates the input that activates that code.IntegrationPackageSearchService.GetIntegrationPackagesWithChannelsAsyncnarrows the channel set toconfiguredChannelwhen non-emptynull; non-C# readsaspire.config.json"channel"→ narrows. If the resolved channel isQuality.Stable, prerelease packages disappear.aspire initstarted writing"channel": "<IdentityChannel>"into scaffoldedaspire.config.jsonfor both single-file C# and polyglot apphosts"channel"populated, activating Layer 1 for polyglot apphosts. C# remains immune via the short-circuit, but only by accident.AddCommandtreated a single fallback candidate as unambiguousaspire add kube --non-interactiveon a TS apphost can silently installAspire.Hosting.Azure. In interactive mode,aspire add kubecan also skip package confirmation whenever fuzzy/no-match fallback collapses to one candidate.13.3.5 users were immune because no path was writing
"channel"intoaspire.config.json— the Layer-1 narrowing existed but never had input. PR #17452 was the activator that woke the dormant bug for everyone on 13.4.Fix #17725 —
IntegrationPackageSearchServiceno longer narrowsDeleted the
configuredChannelnarrowing block. The post-retrieval channel set is now:Result:
"channel"pinned (the Prerelease-only integrations like Aspire.Hosting.Foundry are filtered out of search #17725 case): implicit channel + the pinned channel + any hives. Pre-fix only the pinned channel was searched, so prerelease-only packages (e.g.Aspire.Hosting.Foundry) vanished when pinned toQuality.Stable. Post-fix the implicit channel (Quality.Both) participates, so prerelease packages remain reachable, AND the pinned channel still contributes its feed-specific packages."channel": implicit channel only (matches C# default).GetConfiguredChannelalready short-circuits to null for C#, so the gate behavior is identical to before.The
configuredChannelvalue is still forwarded toPackagingService.GetChannelsAsyncasrequestedChannelNameso out-of-tree apphost staging-channel synthesis (the feature PR #17452 was actually trying to enable) keeps working.Fix #17724 — exact-match guard and interactive confirmation
AddCommandnow tracks whether the candidate set came from exact matching, fuzzy fallback, or the no-match fallback.aspire add <name>fails fast when<name>does not exactly match a friendly name or package ID, instead of silently auto-picking a fuzzy candidate. Callers must supply an exact package ID or friendly name (for example,aspire add kubernetesrather thanaspire add kube).aspire add kubecase from aspire add kube adds first matching integration instead of failing #17724.This is intentionally orthogonal to Fix #17725 — even if discovery returned every relevant package, silently auto-picking a fuzzy match in a script is wrong, and silently adding a single interactive fuzzy/no-match fallback candidate is too surprising.
Intentional behavior changes (for release notes)
"channel"set inaspire.config.jsonpreviously saw packages from only the pinned channel. They now see packages from the implicit channel and the pinned channel.NuGetPackage.Sourcecarries the originating feed through to install, so feed routing on install is preserved.aspire add <fuzzy-name>in non-interactive mode now fails fast instead of silently picking a fuzzy match. Use an exact package ID or friendly name.aspire add <fuzzy-name>in interactive mode now prompts before adding when the result came from fuzzy/no-match fallback, even if only one candidate remains. Exact friendly-name/package-ID matches still do not show the package-selection prompt.Tests
Added/updated in
tests/Aspire.Cli.Tests/Commands/AddCommandTests.cs. All discovery tests use per-channel invocation counters (Interlocked.Incrementinside the cache callback) so that "channel X was searched" is asserted directly rather than inferred from dedupe outcomes.IntegrationSearchCommandFormatJsonWithTypeScriptAppHostPinnedToStableChannelStillSurfacesPrereleaseOnlyPackages— primary regression test for Prerelease-only integrations like Aspire.Hosting.Foundry are filtered out of search #17725. Asserts Foundry surfaces AND both the implicit and stable channels are queried.IntegrationSearchCommandTypeScriptAppHostPersistedChannelExpandsDiscoveryWithoutChangingPreferredResult—[Theory]with two arms. Asserts (a) the user-visible top result is identical with/without"channel"pinned, AND (b) per-channel invocation counters: implicit-only when no pin, implicit + daily when pinned. Fails fast if anyone re-introduces narrowing OR drops the pinned channel.IntegrationSearchCommandFormatJsonWithTypeScriptAppHostPinnedToChannelAlsoSearchesImplicitChannel+...PinnedToStagingChannelAlsoSearchesImplicitChannel— assert both channels are queried (counter-backed) and implicit wins the dedupe.AddCommand_NonInteractive_NoExactMatchWithoutVersion_FailsInsteadOfFuzzyAutoPick_Regression17724— non-interactiveadd kubefails instead of fuzzy auto-picking, andAddPackageAsyncis not called.AddCommand_NonInteractive_ExactMatchWithoutVersion_StillSucceeds— companion happy-path guard for exact friendly-name matching in non-interactive mode.AddCommand_Interactive_SingleFuzzyMatchPromptsBeforeAdding_Regression17724— interactiveadd kubeprompts before adding when fuzzy fallback returns a single candidate.AddCommandInteractiveDoesNotPromptForIntegrationWhenExactMatchIsFound— exact interactive matches skip the package-selection prompt for both friendly names and package IDs.AddCommand_Interactive_NoFuzzyMatchSinglePackagePromptsBeforeAdding— interactive no-match fallback also prompts before adding when only one candidate remains.Two pre-existing
AddCommandFuzzySearchTests(AddCommand_WithStartsWith_FindsMatchUsingFuzzySearch,AddCommand_WithTypo_FindsMatchUsingFuzzySearch) were implicitly testing the buggy auto-pick behavior under the default non-interactive test host. They've been updated to opt into an interactive host environment so they assert the documented interactive-fuzzy-prompt behavior.Latest targeted validation passed for the
AddCommandTestsandAddCommandFuzzySearchTestsclasses.Microsoft Reviewers: Open in CodeFlow