Skip to content

fix(cli): persist channel in aspire.config.json on init and update - #17452

Merged
Mitch Denny (mitchdenny) merged 10 commits into
mainfrom
mitchdenny/aspire-init-channel
May 26, 2026
Merged

fix(cli): persist channel in aspire.config.json on init and update#17452
Mitch Denny (mitchdenny) merged 10 commits into
mainfrom
mitchdenny/aspire-init-channel

Conversation

@mitchdenny

@mitchdenny Mitch Denny (mitchdenny) commented May 24, 2026

Copy link
Copy Markdown
Member

Description

When a non-stable Aspire CLI (daily / staging / pr-<N> / local) ran aspire init, the scaffolded aspire.config.json was written without a top-level channel key. Downstream commands (aspire add, aspire integration list, aspire integration search) then had no channel context for the project and defaulted to the implicit nuget.org channel. The result: a daily-CLI user who ran aspire init followed by aspire add orleans would be offered the stable Orleans package version rather than the daily one that matches the CLI build they are actually dogfooding.

A parallel gap existed on the update path: aspire update --channel <x> against a C# AppHost rewrote NuGet.config and bumped packages, but left aspire.config.json#channel at its create-time value, so subsequent aspire add / aspire update runs against the same project kept resolving against the stale channel. The polyglot (TypeScript) update path in GuestAppHostProject already handled this; ProjectUpdater did not.

This PR fixes both:

Init path

Passes CliExecutionContext.IdentityChannel through to:

  • the single-file C# init path's DropAspireConfig helper, which now writes settings["channel"] when it is not already present
  • the polyglot init path's ScaffoldContext.Channel, which the existing ScaffoldingService already persists into the polyglot template's aspire.config.json (covers TypeScript apphosts as well as any future polyglot languages)

Pre-existing channel values are preserved (the write is gated on settings["channel"] is null for C# and a pre-check in the polyglot path) so user-edited or migrated configs are not clobbered. Only channels that are registered as Explicit in the IPackagingService are persisted, mirroring NewCommand's existing resolution logic — so implicit defaults (e.g. plain stable) are not pinned. Project-mode C# init is unchanged because the aspire-apphost template owns its own aspire.config.json and that mode already produces no top-level config file from init.

Update path

Adds a new ChannelUpdateStep in ProjectUpdater that mirrors GuestAppHostProject.UpdatePackagesInternalAsync: after analysis, when the selected channel is Explicit and differs from the persisted value, the step re-loads aspire.config.json on apply, sets Channel, and saves. Implicit channels are intentionally left alone. The pre-confirm summary lists the channel change alongside any package updates so users see it in the prompt.

Fixes #17295.

User-facing usage

C# single-file apphost

Running a daily CLI build:

aspire init
cat aspire.config.json

Before this change:

{
  "appHost": {
    "path": "apphost.cs"
  },
  "profiles": {
    "https": { /* ... */ },
    "http":  { /* ... */ }
  }
}

After this change:

{
  "appHost": {
    "path": "apphost.cs"
  },
  "channel": "daily",
  "profiles": {
    "https": { /* ... */ },
    "http":  { /* ... */ }
  }
}

TypeScript polyglot apphost

Running a daily CLI build with --language typescript:

aspire init --language typescript
cat aspire.config.json

Before this change:

{
  "appHost": {
    "path": "apphost.mts"
  }
}

After this change:

{
  "appHost": {
    "path": "apphost.mts"
  },
  "channel": "daily"
}

aspire update --channel <x> now rewrites aspire.config.json#channel

For any AppHost layout — C# single-file, C# aspire-empty project mode, TypeScript single-file, TypeScript project mode — running aspire update --channel stable (or --channel daily) against a project pinned to a different Explicit channel now updates aspire.config.json#channel in place, alongside the SDK/package updates.

aspire add orleans from any of these workspaces now resolves Orleans packages against the channel the user just switched to rather than the stale create-time channel.

Tests

  • Existing init tests (C# and polyglot)

  • ProjectUpdaterTests: added unit tests for ChannelUpdateStep.GetFormattedDisplayText (with and without an existing channel value), and extended UpdateProjectFileAsync_CanUpdateFromStableToDaily to assert that aspire.config.json#channel is rewritten from "stable" to "daily"

  • ChannelUpdateWorkflowTests: added three E2E regression tests so the create-path × language matrix is fully covered:

    • UpdateProjectChannelToStable_CSharpSingleFileInit_RewritesAspireConfigChannel
    • UpdateProjectChannelToStable_CSharpEmptyAppHost_RewritesAspireConfigChannel
    • UpdateProjectChannelToStable_TypeScriptSingleFileInit_RewritesAspireConfigChannel

    The existing UpdateProjectChannelToStable_TypeScript_PicksUpStablePackages covers the TS aspire new cell with package-version assertions.

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
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No

`aspire init` was not writing the top-level `channel` key into the
scaffolded `aspire.config.json`. As a result, when a non-stable CLI
build (daily / staging / pr-<N> / local) ran `aspire init`, subsequent
`aspire add` / `integration list` / `integration search` calls had no
channel context and defaulted to implicit nuget.org versions that did
not line up with the CLI build the user was dogfooding.

This change passes `CliExecutionContext.IdentityChannel` through to:

- the single-file C# init path's `DropAspireConfig` helper, which now
  writes `settings["channel"]` when absent
- the polyglot init path's `ScaffoldContext.Channel`, which the
  existing `ScaffoldingService` already persists into the polyglot
  template's `aspire.config.json`

Pre-existing `channel` values are preserved (the write is gated on
`settings["channel"] is null`) so user-edited or migrated configs are
not clobbered.

Related to #17295.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

🚀 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 -- 17452

Or

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 updates aspire init to persist the running CLI’s identity channel (e.g., daily, staging, pr-<N>, local) into the generated aspire.config.json, so subsequent package/integration commands resolve against the intended channel instead of falling back to implicit defaults.

Changes:

  • Single-file C# init now passes CliExecutionContext.IdentityChannel into DropAspireConfig and writes channel when it’s missing.
  • Polyglot init now passes CliExecutionContext.IdentityChannel into ScaffoldContext.Channel so scaffolding persists it.
  • Adds unit tests covering channel persistence + preservation for single-file init.
Show a summary per file
File Description
src/Aspire.Cli/Commands/InitCommand.cs Threads identity channel into init scaffolding/config generation and conditionally writes aspire.config.json#channel.
tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs Adds regression coverage for writing and preserving aspire.config.json#channel in single-file init mode.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/Aspire.Cli/Commands/InitCommand.cs Outdated
Comment thread tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs

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.

1 comment: stale architectural comment in ScaffoldingService contradicts the new polyglot init behavior.

Comment thread src/Aspire.Cli/Commands/InitCommand.cs Outdated
The first pass blindly persisted CliExecutionContext.IdentityChannel into
aspire.config.json#channel. That violates the documented invariant in
ScaffoldingService.cs:84-92 and creates two real regressions:

* Persisting 'local' (or any unregistered identity like a stale 'pr-<N>')
  pins a name no package source mapping can satisfy, zeroing out polyglot
  'aspire add' discovery via IntegrationPackageSearchService's name filter
  (line 28-30).
* Persisting an implicit channel pins the default fallback into the
  project file — exact regression scope of #17295.

Mirror NewCommand.cs:316-402: resolve the identity through
IPackagingService.GetChannelsAsync, match by StringComparisons.ChannelName,
and only persist when the matched channel is PackageChannelType.Explicit.

For the polyglot path also pre-check aspire.config.json#channel before
scaffolding — ScaffoldingService writes context.Channel unconditionally
when non-empty, so without this guard a user-edited value would be
silently overwritten on subsequent 'aspire init' runs.

Adds coverage for: unregistered identity (single-file + polyglot),
implicit-channel identity (single-file), and polyglot channel
pass-through + preserve-existing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny

Copy link
Copy Markdown
Member Author

Self-review post-mortem (commit 15add1be4)

Caught three issues in the first pass that warranted a follow-up commit. Recording the analysis here so the rationale lives with the PR.

1. 🔴 Polyglot path blindly persisted IdentityChannel, violating a documented invariant

InitCommand.cs was passing _executionContext.IdentityChannel straight into ScaffoldContext.Channel, which ScaffoldingService.cs:95 then wrote verbatim to aspire.config.json#channel. But ScaffoldingService.cs:84–92 explicitly warns against exactly that:

"Do NOT fall back to CliExecutionContext.IdentityChannel: an identity that isn't a registered channel … would otherwise pin a channel name that no PSM rule can satisfy."

NewCommand.cs:316–402 handles this correctly — enumerate channels via PackagingService.GetChannelsAsync, match identity against registered channels, and only persist when the match is Explicit. The first pass skipped all of that.

2. 🔴 Persisting "local" (or any unregistered identity) breaks polyglot aspire add entirely

CliExecutionContext.IdentityChannel defaults to "local". IntegrationPackageSearchService.cs:28–30 filters allChannels to those whose Name equals the configured channel. "local" isn't a registered channel name in most setups → filter returns zero packages → polyglot aspire add shows nothing. Same failure mode for a stale pr-<N> identity on a machine without the matching hive.

3. 🟡 Polyglot path silently overwrote user-edited channel

The first pass added a settings["channel"] is null guard inside DropAspireConfig, but the polyglot path goes through ScaffoldingService.cs:93–95 which writes config.Channel = context.Channel unconditionally when non-empty. A user who hand-edited a polyglot aspire.config.json#channel would lose their value on the next aspire init.

Fix (commit 15add1be4)

  • Inject IPackagingService into InitCommand.
  • New helper ResolvePersistableChannelNameAsync mirrors NewCommand.cs:316–402: matches identity against GetChannelsAsync results via StringComparisons.ChannelName, returns the name only when the match is Explicit, returns null for unregistered or Implicit matches.
  • Single-file C# path: route the resolved value (not raw IdentityChannel) into DropAspireConfig.
  • Polyglot path: pre-check AspireConfigFile.Load(...)?.Channel; if a value already exists, pass null into ScaffoldContext.Channel so ScaffoldingService leaves the user's value alone.
  • Added 5 tests:
    • …DoesNotPersistChannelWhenIdentityUnregistered (single-file)
    • …DoesNotPersistChannelWhenIdentityMatchesImplicit (single-file)
    • …PolyglotMode_PassesResolvedChannelToScaffolder
    • …PolyglotMode_PreservesExistingChannelInAspireConfig
    • …PolyglotMode_DoesNotPassChannelWhenIdentityUnregistered

54/54 InitCommandTests pass; 237/237 across Init / New / Add / Scaffolding / channel-resolution / template-persistence test classes.

@mitchdenny

Copy link
Copy Markdown
Member Author

/deployment-test

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #17452...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 25, 2026 01:02 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot had a problem deploying to deployment-testing May 25, 2026 01:02 Failure
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing May 25, 2026 01:02 Inactive
Comment thread src/Aspire.Cli/Commands/InitCommand.cs
Comment thread src/Aspire.Cli/Projects/ProjectUpdater.cs
Centralize the channel persistence rule so stable is treated like the default public-feed behavior for init and update paths.

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.

Only one ChannelUpdateStep can ever be enqueued per 'aspire update'
invocation (one AppHost project per invocation), so the foreach +
Count guard was over-general. Use SingleOrDefault to make the
invariant obvious.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny
Mitch Denny (mitchdenny) force-pushed the mitchdenny/aspire-init-channel branch from a1e12b7 to 8377117 Compare May 26, 2026 00:25
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mitchdenny

Copy link
Copy Markdown
Member Author

Updated the stale ChannelUpdateWorkflowTests expectations in 3a21827. The CI TRX showed UpdateProjectChannelToStable_CSharpSingleFileInit_RewritesAspireConfigChannel failing because aspire.config.json#channel stayed pr-17452 instead of stable, and the two TypeScript cases timing out waiting for Update successful after the stable-package restore could not use stable versions from the preserved PR channel mapping. The tests now preview the stable update, assert no aspire.config.json#channel rewrite is enqueued, decline the previewed package changes, and verify the existing channel/package values are preserved; the listed unknown recordings I checked had passing TRX results and appear unrelated to this PR's test assertions. Validation: MSBUILDTERMINALLOGGER=false ./dotnet.sh build tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj /p:SkipNativeBuild=true succeeded.

@mitchdenny

Copy link
Copy Markdown
Member Author

🧪 Dogfood Test Report — head 3a218270

Ran a focused smoke pass on the dogfood CLI for this PR.

CLI Version Verification

  • PR head SHA: 3a218270fa498dccf0ed85eabf538747d61723a9
  • Installed CLI reports: 13.4.0-pr.17452.g3a218270
  • Match:

Scenarios (4/4 passed)

# Scenario Result
A aspire init --language csharp persists channel: "pr-17452" in aspire.config.json
B aspire init --language typescript persists channel: "pr-17452" in aspire.config.json
C aspire update --channel stable --yes applies package updates but preserves channel: "pr-17452" (F1 fix — does not narrow polyglot to nuget.org-only)
D aspire update --channel daily --yes rewrites channel from pr-17452 to daily (Explicit non-stable channel still persists)

Evidence

Scenario Caspire update --channel stable --yes pre-confirmation summary on a project initialized at channel: "pr-17452":

📦 Aspire.AppHost.Sdk 13.4.0-pr.17452.g3a218270 to 13.3.5

Applying updates...
Executing: Update package Aspire.AppHost.Sdk from 13.4.0-pr.17452.g3a218270 to 13.3.5
Restoring packages...
✅ Update successful!

No aspire.config.json#channel pr-17452 to stable line appears — the unified PackageChannel.ShouldPersistChannelName() filter correctly dropped the channel rewrite step. Channel after apply: pr-17452 (preserved).

Scenario D — same starting state, --channel daily instead:

📦 aspire.config.json#channel pr-17452 to daily

Applying updates...
Executing: Update package Aspire.AppHost.Sdk from 13.4.0-pr.17452.g3a218270 to 13.4.0-preview.1.26275.2
Executing: Update aspire.config.json channel from 'pr-17452' to 'daily'
Restoring packages...
✅ Update successful!

Single channel-update line renders cleanly via the new SingleOrDefault path. Channel after apply: daily.

Conclusion

The init + update behavior on the latest commit (3a218270) is consistent with prior validation on 9adaf5ab8. The two newer commits (8377117 SingleOrDefault refactor + 3a21827 test-expectation update) are non-behavioral and verified by the Scenario D rendering check.

Result: ✅ PR verified

@mitchdenny

Copy link
Copy Markdown
Member Author

This CI E2E run was against 9adaf5a. The three failing tests (UpdateProjectChannelToStable_CSharpSingleFileInit_RewritesAspireConfigChannel, UpdateProjectChannelToStable_TypeScriptSingleFileInit_RewritesAspireConfigChannel, UpdateProjectChannelToStable_TypeScript_PicksUpStablePackages) were already addressed in the very next commit 3a21827 ("test(cli): update channel update E2E expectations"), which rewrites the test expectations to match the new "stable is intentionally not persisted" semantics introduced by the unified PackageChannel.ShouldPersistChannelName() helper. The follow-up E2E run against the current head should be clean.

@radical

Copy link
Copy Markdown
Member

aspire init with build from this PR, followed by aspire update --channel stable fails.

https://asciinema.org/a/0CIv9eltzcleiTq5

Screenshot 2026-05-25 at 21 03 49

@mitchdenny

Copy link
Copy Markdown
Member Author
Screenshot 2026-05-26 at 11 36 04 am

Worked on my machine

@mitchdenny

Copy link
Copy Markdown
Member Author

CI investigation: the remaining failure is unrelated to this PR.

Failed job: Cli.EndToEnd-ResourceCommandTests / ubuntu-latest.

Failed test: ResourceCommandTests.ResourceCommand_FailsWhenInteractionServiceIsRequired (exercises aspire resource cache needs-interaction failure surface; does not touch channel-persistence code).

Failure point: step 72/72, WaitUntilText(" stopped successfully.") after aspire stop timed out after 60s. Total test duration was 9m45s — looks like an overloaded ubuntu runner and the AppHost did not shut down within the 60s budget. Looks like flake / slow-runner contention, not a regression from this PR.

Reran the failed job. Will report back if it reproduces on the rerun.

@mitchdenny

Copy link
Copy Markdown
Member Author

✅ Rerun is clean — ResourceCommandTests.ResourceCommand_FailsWhenInteractionServiceIsRequired passed on the second attempt. Confirmed flake, unrelated to this PR. PR is now 309/309 green.

@mitchdenny

Copy link
Copy Markdown
Member Author

Investigation: ResourceCommand_FailsWhenInteractionServiceIsRequired failure

I looked at the failing CLI E2E test (Tests / Cli.EndToEnd-ResourceCommandTests) and dug into the recordings/logs for run 26425521402.

TL;DR

This is an intermittent, pre-existing flake unrelated to this PR. Recommend quarantining and tracking separately.

Attempt history (correction)

Attempt Commit ResourceCommandTests result
1 9adaf5a ❌ failed
2 3a21827 passed
3 3a21827 🟡 in progress at time of writing

So it's actually 1 fail / 1 pass — classic flake, not a deterministic repro.

Failure signature (from cast file)

The test does not "time out waiting for nothing" — aspire stop actively prints:

⠳ Stopping apphost.cs...
❌ Failed to stop apphost.cs.
📄 See logs at /root/.aspire/logs/cli_20260526T005156_f5571f28.log
🔍 See AppHost logs at /root/.aspire/logs/cli_20260526T005143512_detach-child_2b8aff7ddcde490a851a3d6b735b842f.log

after spinning for ~60s. The 60s WaitUntilText(" stopped successfully.") timeout in the test then fires.

At the moment of failure, the post-job docker container ls from the same CI step shows the redis container still running:

CONTAINER ID   IMAGE          STATUS         NAMES
5aa9f3938387   redis:latest   Up 7 seconds   cache-xuhbbbzq

So ProcessShutdownService.StopProcessesAsync exhausted both the 10s graceful window and the post-force-kill monitor window without DCP finishing the container teardown, and returned falseStopCommand printed FailedToStopAppHost. This is a real CLI/DCP shutdown timing failure under Docker contention, not a test-only timing bug.

Why this is not caused by PR #17452

This PR only touches CLI init / channel persistence / scaffolding paths:
InitCommand.cs, PackageChannel.cs, GuestAppHostProject.cs, ProjectUpdater.cs, ScaffoldingService.cs (+ resx/xlf + test files).

None of these are on the code path for:

  • aspire stop (StopCommand, ProcessShutdownService, AppHostAuxiliaryBackchannel.StopAppHostAsync)
  • DCP-managed container shutdown
  • Backchannel/RPC teardownThe failing scenario is the post-needs-interaction shutdown after the AppHost has a live redis container — pure runtime-side behavior the PR cannot influence.

Why the test is plausibly racy

The test was added on 2026-05-13 in #16973 and is the only ResourceCommandTests case that:

  1. Uses mountDockerSocket: true and runs a real redis container.
  2. Triggers a deliberate command failure path (IInteractionService unavailable) immediately before aspire stop.

When DCP-managed container teardown is slow under Docker-in-Docker contention (which is the normal CI condition for these tests), ProcessShutdownService's 10s graceful + force-kill + monitor budget can be exhausted before the AppHost process exits.

The companion test ResourceCommand_FailedExecution_DisplaysAppHostLogPathAndLogContainsEntries exercises a very similar shape (redis container + failing resource command + aspire stop) and passed in the same run, which is consistent with intermittent shutdown timing rather than a deterministic regression.

Recommendation

  • Do not block this PR on the failure: there is no plausible causal link to the PR's changes.
  • Quarantine Aspire.Cli.EndToEnd.Tests.ResourceCommandTests.ResourceCommand_FailsWhenInteractionServiceIsRequired against a new tracking issue, and consider whether ProcessShutdownService should give DCP-managed-container scenarios a longer monitor window (or whether the test should pass a longer per-call cancellation to aspire stop).

I did not run local reproduction — the failure is intermittent across CI attempts and would require many runs in the linux-x64 CLI E2E container image to hit; the API-derived attempt history above is stronger evidence than a single local pass/fail anyway. Happy to file the quarantine PR + tracking issue if you'd like.

@mitchdenny

Copy link
Copy Markdown
Member Author

Quarantine in flight — you can stop respinning attempts on this PR for the ResourceCommandTests job once the quarantine merges.

Also updating the attempt-count from my earlier comment: attempt 3 came back failed (same shape), so it's 2F/1P on this run — still intermittent, conclusion unchanged.

@github-actions

Copy link
Copy Markdown
Contributor

CLI E2E Tests failed — 97 passed, 1 failed, 6 unknown (commit 3a21827)

Failed Tests

View all recordings
Status Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View recording
AddPackageWhileAppHostRunningDetached ▶️ View recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View recording
AgentInitCommand_DefaultSelection_InstallsDefaultSkills ▶️ View recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View recording
AgentMcpListStructuredLogsFromStarterAppCore ▶️ View recording
AllPublishMethodsBuildDockerImages ▶️ View recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View recording
AspireInitSingleFileAppHostRunsViaDotnetRunAppHost ▶️ View recording
AspireInitWithExistingAppHostDirRecreatesMissingNuGetConfigAndPreservesFiles ▶️ View recording
AspireInitWithSolutionFileGeneratesAppHostThatBuildsAgainstChannelHive ▶️ View recording
AspireStartUpdatesStaleTypeScriptAppHostPath ▶️ View recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View recording
AspireUpdateRemovesOrphanAppHostPackageVersionWhenSdkAlreadyCurrent ▶️ 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_UsesConfiguredToolchain ▶️ View recording
DashboardRunWithAgentMcpCore ▶️ View recording
DashboardRunWithOtelTracesReturnsNoTracesCore ▶️ View recording
DeployK8sBasicApiService ▶️ View recording
DeployK8sWithExternalHelmChart ▶️ 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
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ View recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View recording
GeneratedAspireDevScript_StartsWatchMode_WithConfiguredToolchain ▶️ 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
InteractiveCSharpInitCreatesExpectedFiles ▶️ View recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View recording
JavaScriptHostingApisRunFromTypeScriptAppHost ▶️ View recording
LatestCliCanStartStableChannelAppHost ▶️ View recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View recording
LogLevelTrace_ProducesTraceEntriesInCliLogFile ▶️ View recording
LogsCommandShowsResourceLogs ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterAppIsolated ▶️ View recording
PsCommandListsRunningAppHost ▶️ View recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View recording
PublishJavaScriptPatternsGeneratesExpectedDockerComposeArtifacts ▶️ View recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View recording
ResourceCommand_FailedExecution_DisplaysAppHostLogPathAndLogContainsEntries ▶️ View recording
ResourceCommand_FailsWhenInteractionServiceIsRequired ▶️ View failure recording
ResourceCommand_SetAndDeleteParameterUpdatesDescribeOutput ▶️ View recording
RestoreGeneratesSdkFiles ▶️ View recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ View recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View recording
RunPublishFailureScenarioAsync ▶️ View recording
RunReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
RunReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
SecretCrudOnDotNetAppHost ▶️ View recording
SecretCrudOnTypeScriptAppHost ▶️ View recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View recording
StartReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
StartReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
StopAllAppHostsFromAppHostDirectory ▶️ View recording
StopJavaPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopNonInteractiveSingleAppHost ▶️ View recording
StopTypeScriptPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View recording
UpdateProjectChannelToStable_CSharpEmptyAppHost_PreservesAspireConfigChannel ▶️ View recording
UpdateProjectChannelToStable_CSharpSingleFileInit_PreservesAspireConfigChannel ▶️ View recording
UpdateProjectChannelToStable_TypeScriptSingleFileInit_PreservesAspireConfigChannel ▶️ View recording
UpdateProjectChannelToStable_TypeScript_PreviewsStablePackagesAndPreservesChannel ▶️ View recording

📹 Recordings uploaded automatically from CI run #26425521402

@mitchdenny
Mitch Denny (mitchdenny) merged commit 60c3da4 into main May 26, 2026
1229 of 1235 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 26, 2026
aspire-repo-bot Bot added a commit to microsoft/aspire.dev that referenced this pull request May 26, 2026
…t and update

Document that aspire init persists the CLI identity channel into
aspire.config.json when using a non-stable channel (daily, staging,
pr-N), and that aspire update --channel also rewrites the channel
key in aspire.config.json so subsequent commands use the updated channel.

Documents changes from microsoft/aspire#17452.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Pull request created: #1068

Generated by PR Documentation Check

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1068 targeting release/13.4.

Updated two CLI command reference pages to document the channel persistence behavior introduced in #17452:

  • aspire-init.mdx: Added a Channel persistence in aspire.config.json section explaining that aspire init now writes the channel key into aspire.config.json when using a non-stable explicit channel, with a JSON example and notes on preservation and pinning behavior.
  • aspire-update.mdx: Extended the --channel option description to document that switching channels also rewrites aspire.config.json#channel so subsequent commands resolve packages against the updated channel.

Note

This draft PR needs human review before merging.

David Fowler (davidfowl) added a commit that referenced this pull request May 30, 2026
…fuzzy auto-pick (#17724, #17725) (#17728)

* fix(cli): restore implicit-channel discovery + guard non-interactive 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>

* fix(cli): restore explicit-channel inclusion when polyglot apphost pins 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>

* test(cli): pin staging-shipping behavior for #17724 + #17725

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>

* Fix interactive fuzzy add confirmation

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>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: David Fowler <davidfowl@gmail.com>
David Pine (IEvangelist) added a commit to microsoft/aspire.dev that referenced this pull request Jun 1, 2026
…it and update (#1068)

* docs(cli): document channel persistence in aspire.config.json for init and update

Document that aspire init persists the CLI identity channel into
aspire.config.json when using a non-stable channel (daily, staging,
pr-N), and that aspire update --channel also rewrites the channel
key in aspire.config.json so subsequent commands use the updated channel.

Documents changes from microsoft/aspire#17452.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review feedback (4 threads)

- Remove undocumented PR-channel example (PRRT_kwDOQK_VN86Eyhhb)

- Show generated profile applicationUrl and environmentVariables (PRRT_kwDOQK_VN86FtdNt)

- Clarify explicit-channel persistence and stable exception for init (PRRT_kwDOQK_VN86FtdNz)

- Clarify update only rewrites existing config and stable is not pinned (PRRT_kwDOQK_VN86FtdN4)

Verified against microsoft/aspire@16ecffd on branch main.

Edited per the doc-writer skill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Apply suggestion from @IEvangelist

---------

Co-authored-by: aspire-repo-bot[bot] <268009190+aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: David Pine <david.pine@microsoft.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

4 participants