Skip to content

Refuse synthesized 'staging' channel on daily CLI builds#16717

Open
mitchdenny wants to merge 2 commits intomainfrom
mitchdenny/fix-16652-update-channel-staging-daily-cli
Open

Refuse synthesized 'staging' channel on daily CLI builds#16717
mitchdenny wants to merge 2 commits intomainfrom
mitchdenny/fix-16652-update-channel-staging-daily-cli

Conversation

@mitchdenny
Copy link
Copy Markdown
Member

@mitchdenny mitchdenny commented May 4, 2026

Description

Fixes #16652.

PackagingService synthesized the staging channel relative to the running CLI build:

  • For stable-quality staging it built a darc feed URL from the current CLI''s commit hash (darc-pub-microsoft-aspire-{currentCliCommitHash}).
  • For prerelease/Both staging quality without an explicit feed override it fell back to the shared daily feed.

When the running CLI is itself a daily build, neither produces a real staging feed: there is no SHA-specific darc feed for daily commits, and the shared daily feed contains daily packages. As a result aspire update --channel staging silently resolved to daily versions — the bug tracked by #16652.

This PR makes staging resolution deterministic on daily CLI builds by refusing to synthesize the channel rather than silently downgrading:

  1. New IPackagingService.GetStagingChannelUnavailableReason() returns a localized, user-facing explanation when staging cannot be resolved.
  2. CreateStagingChannel() consults that helper. If staging is unavailable on a daily CLI it logs a warning and returns null instead of fabricating a daily feed.
  3. UpdateCommand surfaces the same reason in its ChannelNotFoundException message when the user explicitly passes --channel staging, so the failure is clear and actionable.
  4. CreateStagingChannel() always logs the resolved feed URL + quality + pinned version when it does produce a channel (the "show what was resolved" suggestion from the issue).

Escape hatches and unaffected paths:

  • An explicit overrideStagingFeed configuration entry continues to take ownership; staging is allowed in that case regardless of CLI build.
  • Stable releases and "blessed" prereleases (preview.1, rc.1) continue to produce a real staging channel.
  • Only Arcade-style 3+ identifier prerelease versions (e.g. preview.1.26210.1) are treated as daily.
  • Unparseable / null / missing CLI versions are treated as daily (fail-safe, refuses to silently downgrade).

This is a 💥 blocking-release fix targeting main first; a backport PR to release/13.3 will follow.

Validation

To verify locally, install the CLI from this PR:

# Linux/macOS
./eng/scripts/get-aspire-cli-pr.sh 16717

# Windows PowerShell
.\eng\scripts\get-aspire-cli-pr.ps1 16717

Then:

# 1. Confirm a daily CLI now refuses --channel staging
aspire update --channel staging
# Expected: ChannelNotFoundException with the localized
#   "staging channel unavailable on this daily CLI" message,
#   pointing at overrideStagingFeed as the recovery path.

# 2. Confirm overrideStagingFeed still allows it
aspire config set overrideStagingFeed https://pkgs.dev.azure.com/dnceng/...
aspire update --channel staging
# Expected: succeeds, logs "Resolved 'staging' channel: feed=..., quality=..., pinnedVersion=...".

# 3. Confirm channel-less aspire update is unaffected on a daily CLI
aspire config unset overrideStagingFeed
aspire update
# Expected: unchanged behavior; only the explicit --channel staging path is touched.

A stable / blessed-preview CLI is unaffected — staging continues to resolve via the SHA-specific darc feed.

Automated coverage

  • tests/Aspire.Cli.Tests/Packaging/PackagingServiceTests.cs (new file, ~280 lines) covers staging behavior across stable / blessed-preview / daily CLI versions, with and without overrideStagingFeed, including the unparseable-version fail-safe.
  • tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs covers --channel staging surfacing the staging-specific exception message instead of the generic "no channel matching" message.
  • A test seam (internal:packaging:cliVersionForTesting config key) lets unit tests deterministically simulate stable, blessed-preview, and daily CLI builds without depending on the actual Aspire.Cli.dll assembly version. The key is internal/test-only, scoped to PackagingConfigurationKeys, and clearly documented as such.

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
  • Does the change require an update in our Aspire docs?
    • Yes
    • No

On a daily Aspire CLI, `aspire update --channel staging` could resolve to
daily packages because PackagingService.CreateStagingChannel() either built a
per-commit darc-pub feed URL from the daily commit hash (no such feed exists)
or fell back to the shared dotnet9 daily feed. Either way the user got daily
packages instead of staging packages with no warning.

PackagingService now refuses to synthesize the staging channel when the
running CLI is itself a daily/CI build (detected via prerelease-identifier
count on the assembly informational version) unless the user provides an
explicit overrideStagingFeed configuration value. The resolved staging feed
URL is also logged at info level so users can verify channel resolution.

When the staging channel is omitted on a daily CLI, `aspire update --channel
staging` now surfaces an actionable error explaining why and how to recover
(set 'overrideStagingFeed' or use a stable CLI build).

Fixes #16652

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 4, 2026 05:56
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 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 -- 16717

Or

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

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

Prevents aspire update --channel staging from silently resolving to daily packages when the running CLI is itself a daily build. The PR adds staging-unavailable detection in PackagingService, reuses that reason in UpdateCommand, and adds regression tests plus localized resource entries.

Changes:

  • Added a new staging-unavailable reason API and daily-build heuristic in PackagingService.
  • Updated UpdateCommand to surface a staging-specific error instead of the generic missing-channel message.
  • Added regression tests and new packaging resource strings for the user-facing message.

Reviewed changes

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

Show a summary per file
File Description
tests/Aspire.Cli.Tests/TestServices/TestPackagingService.cs Extended test fake to expose staging-unavailable reason.
tests/Aspire.Cli.Tests/Packaging/PackagingServiceTests.cs Added regression coverage for daily/stable/blessed staging resolution cases.
tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs Added test for explicit staging error message.
src/Aspire.Cli/Resources/xlf/PackagingStrings.zh-Hant.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.zh-Hans.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.tr.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.ru.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.pt-BR.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.pl.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.ko.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.ja.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.it.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.fr.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.es.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.de.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/xlf/PackagingStrings.cs.xlf Added localized staging-unavailable string entry.
src/Aspire.Cli/Resources/PackagingStrings.resx Added source resource string for staging-unavailable message.
src/Aspire.Cli/Resources/PackagingStrings.Designer.cs Regenerated strongly-typed resource accessor.
src/Aspire.Cli/Packaging/PackagingService.cs Added daily-build gating, test seam, logging, and staging-unavailable reason generation.
src/Aspire.Cli/Commands/UpdateCommand.cs Switched missing-channel message construction to use staging-specific reason.
Files not reviewed (1)
  • src/Aspire.Cli/Resources/PackagingStrings.Designer.cs: Language not supported

Comment on lines +117 to +123
// If the user has supplied an explicit staging feed override they are taking
// ownership of where staging packages come from, so any CLI build is allowed.
var hasExplicitFeedOverride = !string.IsNullOrEmpty(configuration["overrideStagingFeed"]);
if (hasExplicitFeedOverride)
{
return null;
}
Comment on lines +193 to +203
/// Honors the test-only configuration override so tests can deterministically
/// simulate stable / blessed-prerelease / daily CLI builds.
/// </summary>
private string? GetCliInformationalVersionForStagingDecision()
{
var testOverride = configuration[PackagingConfigurationKeys.CliVersionForTesting];
if (!string.IsNullOrWhiteSpace(testOverride))
{
return testOverride;
}

Comment on lines +181 to 190
// CLI logs (suggested-fix option 3 from #16652).
logger.LogInformation(
"Resolved 'staging' channel: feed='{StagingFeedUrl}', quality='{Quality}', pinnedVersion='{PinnedVersion}'.",
stagingFeedUrl,
stagingQuality,
pinnedVersion ?? "(none)");

return stagingChannel;
}

Comment on lines +285 to +300
private string BuildChannelNotFoundMessage(string channelName, IEnumerable<PackageChannel> allChannels)
{
// For an explicit `--channel staging` request, surface the staging-specific
// unavailability reason (issue #16652) instead of the generic "no channel
// matching" message so users on a daily CLI know why the channel was omitted
// and how to recover (set 'overrideStagingFeed' or use a stable CLI).
if (string.Equals(channelName, PackageChannelNames.Staging, StringComparison.OrdinalIgnoreCase))
{
var stagingReason = _packagingService.GetStagingChannelUnavailableReason();
if (!string.IsNullOrEmpty(stagingReason))
{
return stagingReason;
}
}

return $"No channel found matching '{channelName}'. Valid options are: {string.Join(", ", allChannels.Select(c => c.Name))}";
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

Re-running the failed jobs in the CI workflow for this pull request because 2 jobs were 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.

…nism

The 8 pre-existing tests in PackagingServiceTests that exercise the
shared-feed and pin-to-CLI-version paths assume the running CLI is not
itself a daily/CI build, but they don't pin the version. With this PR's
new daily-CLI guard, when CI builds the test assembly with a
daily-flavored VersionSuffix (e.g. pr.NNNN.gSHA), the daily heuristic
treats the running CLI as daily and GetStagingChannelUnavailableReason
hides the staging channel, causing the tests to fail with "Sequence
contains no matching element" on .First(c => c.Name == "staging").

Use the existing internal:packaging:cliVersionForTesting test seam to
pin a stable CLI version (13.4.0) in those tests so the daily-CLI guard
doesn't trigger and the tests deterministically exercise the shared-feed
and pinning code paths regardless of the test-assembly's build flavor.

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

github-actions Bot commented May 5, 2026

🎬 CLI E2E Test Recordings — 76 recordings uploaded (commit 560b48f)

View all recordings
Status 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_UsesConfiguredToolchain ▶️ 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
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ 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
InteractiveCSharpInitCreatesExpectedFiles ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LatestCliCanStartStableChannelAppHost ▶️ View Recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterAppCore ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ 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
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #25360887354

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 update --channel staging can select daily package versions from a daily CLI

2 participants