From 1270a9ff8d0b2262d5d5f23f44111c6bbaa360d0 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Mar 2026 06:24:35 +1100 Subject: [PATCH 1/3] Remove mature feature flags that no longer need toggles Remove four feature flags that have been stable and always-on (default: true) for months, making their escape-hatch toggle unnecessary: - minimumSdkCheckEnabled: SDK version check always runs (stable 7+ months) - orphanDetectionWithTimestampEnabled: timestamp always set (zero-cost robustness) - runningInstanceDetectionEnabled: instance detection always active (stable since Dec 2025) - packageSearchDiskCachingEnabled: disk caching always used (purely beneficial) The gated behaviors are now unconditional. IFeatures parameters removed from DotNetSdkInstaller, DotNetCliExecutionFactory, and AppHostLauncher where they were only used for the removed flags. Fixes #14966 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../aspire-global-settings.schema.json | 64 ------------------- extension/schemas/aspire-settings.schema.json | 64 ------------------- src/Aspire.Cli/Commands/AddCommand.cs | 29 ++++----- src/Aspire.Cli/Commands/AppHostLauncher.cs | 5 +- src/Aspire.Cli/Commands/RunCommand.cs | 23 +++---- .../DotNet/DotNetCliExecutionFactory.cs | 7 +- src/Aspire.Cli/DotNet/DotNetCliRunner.cs | 2 +- src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs | 9 +-- src/Aspire.Cli/KnownFeatures.cs | 26 +------- .../Commands/RunCommandTests.cs | 15 ----- .../DotNetSdkInstallerTests.cs | 56 +++------------- 11 files changed, 36 insertions(+), 264 deletions(-) diff --git a/extension/schemas/aspire-global-settings.schema.json b/extension/schemas/aspire-global-settings.schema.json index 03ccb0aa580..d83898bdf5c 100644 --- a/extension/schemas/aspire-global-settings.schema.json +++ b/extension/schemas/aspire-global-settings.schema.json @@ -109,70 +109,6 @@ "description": "Enable or disable experimental Rust language support for polyglot Aspire applications", "default": false }, - "minimumSdkCheckEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable minimum .NET SDK version checking before running Aspire applications", - "default": true - }, - "orphanDetectionWithTimestampEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable timestamp-based orphan process detection to clean up stale Aspire processes", - "default": true - }, - "packageSearchDiskCachingEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable disk caching for package search results to improve performance", - "default": true - }, - "runningInstanceDetectionEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable detection of already running Aspire instances to prevent conflicts", - "default": true - }, "showAllTemplates": { "anyOf": [ { diff --git a/extension/schemas/aspire-settings.schema.json b/extension/schemas/aspire-settings.schema.json index b15d09198d7..0eb45d95814 100644 --- a/extension/schemas/aspire-settings.schema.json +++ b/extension/schemas/aspire-settings.schema.json @@ -113,70 +113,6 @@ "description": "Enable or disable experimental Rust language support for polyglot Aspire applications", "default": false }, - "minimumSdkCheckEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable minimum .NET SDK version checking before running Aspire applications", - "default": true - }, - "orphanDetectionWithTimestampEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable timestamp-based orphan process detection to clean up stale Aspire processes", - "default": true - }, - "packageSearchDiskCachingEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable disk caching for package search results to improve performance", - "default": true - }, - "runningInstanceDetectionEnabled": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "enum": [ - "true", - "false" - ] - } - ], - "description": "Enable or disable detection of already running Aspire instances to prevent conflicts", - "default": true - }, "showAllTemplates": { "anyOf": [ { diff --git a/src/Aspire.Cli/Commands/AddCommand.cs b/src/Aspire.Cli/Commands/AddCommand.cs index 8e557adb593..adfdc50f524 100644 --- a/src/Aspire.Cli/Commands/AddCommand.cs +++ b/src/Aspire.Cli/Commands/AddCommand.cs @@ -26,7 +26,6 @@ internal sealed class AddCommand : BaseCommand private readonly IAddCommandPrompter _prompter; private readonly IDotNetSdkInstaller _sdkInstaller; private readonly ICliHostEnvironment _hostEnvironment; - private readonly IFeatures _features; private readonly IAppHostProjectFactory _projectFactory; private static readonly Argument s_integrationArgument = new("integration") @@ -52,7 +51,6 @@ public AddCommand(IPackagingService packagingService, IInteractionService intera _prompter = prompter; _sdkInstaller = sdkInstaller; _hostEnvironment = hostEnvironment; - _features = features; _projectFactory = projectFactory; Arguments.Add(s_integrationArgument); @@ -210,22 +208,19 @@ await Parallel.ForEachAsync(channels, cancellationToken, async (channel, ct) => // Stop any running AppHost instance before adding the package. // A running AppHost (especially in detach mode) locks project files, // which prevents 'dotnet add package' from modifying the project. - if (_features.IsFeatureEnabled(KnownFeatures.RunningInstanceDetectionEnabled, defaultValue: true)) - { - var runningInstanceResult = await project.FindAndStopRunningInstanceAsync( - effectiveAppHostProjectFile, - ExecutionContext.HomeDirectory, - cancellationToken); + var runningInstanceResult = await project.FindAndStopRunningInstanceAsync( + effectiveAppHostProjectFile, + ExecutionContext.HomeDirectory, + cancellationToken); - if (runningInstanceResult == RunningInstanceResult.InstanceStopped) - { - InteractionService.DisplayMessage(KnownEmojis.Information, AddCommandStrings.StoppedRunningInstance); - } - else if (runningInstanceResult == RunningInstanceResult.StopFailed) - { - InteractionService.DisplayError(AddCommandStrings.UnableToStopRunningInstances); - return ExitCodeConstants.FailedToAddPackage; - } + if (runningInstanceResult == RunningInstanceResult.InstanceStopped) + { + InteractionService.DisplayMessage(KnownEmojis.Information, AddCommandStrings.StoppedRunningInstance); + } + else if (runningInstanceResult == RunningInstanceResult.StopFailed) + { + InteractionService.DisplayError(AddCommandStrings.UnableToStopRunningInstances); + return ExitCodeConstants.FailedToAddPackage; } var success = await InteractionService.ShowStatusAsync( diff --git a/src/Aspire.Cli/Commands/AppHostLauncher.cs b/src/Aspire.Cli/Commands/AppHostLauncher.cs index fac027e55db..47b6b1d042f 100644 --- a/src/Aspire.Cli/Commands/AppHostLauncher.cs +++ b/src/Aspire.Cli/Commands/AppHostLauncher.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.Text.Json; using Aspire.Cli.Backchannel; -using Aspire.Cli.Configuration; using Aspire.Cli.Interaction; using Aspire.Cli.Processes; using Aspire.Cli.Projects; @@ -24,7 +23,6 @@ namespace Aspire.Cli.Commands; internal sealed class AppHostLauncher( IProjectLocator projectLocator, CliExecutionContext executionContext, - IFeatures features, IInteractionService interactionService, IAuxiliaryBackchannelMonitor backchannelMonitor, ILogger logger, @@ -150,12 +148,11 @@ public async Task LaunchDetachedAsync( private async Task StopExistingInstancesAsync(FileInfo effectiveAppHostFile, CancellationToken cancellationToken) { - var runningInstanceDetectionEnabled = features.IsFeatureEnabled(KnownFeatures.RunningInstanceDetectionEnabled, defaultValue: true); var existingSockets = AppHostHelper.FindMatchingSockets( effectiveAppHostFile.FullName, executionContext.HomeDirectory.FullName); - if (runningInstanceDetectionEnabled && existingSockets.Length > 0) + if (existingSockets.Length > 0) { logger.LogDebug("Found {Count} running instance(s) for this AppHost, stopping them first.", existingSockets.Length); var manager = new RunningInstanceManager(logger, interactionService, timeProvider); diff --git a/src/Aspire.Cli/Commands/RunCommand.cs b/src/Aspire.Cli/Commands/RunCommand.cs index e3646880bb8..d2b9a4efa13 100644 --- a/src/Aspire.Cli/Commands/RunCommand.cs +++ b/src/Aspire.Cli/Commands/RunCommand.cs @@ -140,9 +140,8 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell Debug.Assert(_startDebugSessionOption is not null); startDebugSession = parseResult.GetValue(_startDebugSessionOption); } - var runningInstanceDetectionEnabled = _features.IsFeatureEnabled(KnownFeatures.RunningInstanceDetectionEnabled, defaultValue: true); // Force option kept for backward compatibility but no longer used since prompt was removed - // var force = runningInstanceDetectionEnabled && parseResult.GetValue("--force"); + // var force = parseResult.GetValue("--force"); // Validate that --format is only used with --detach if (format == OutputFormat.Json && !detach) @@ -198,19 +197,15 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell return ExitCodeConstants.FailedToFindProject; } - // Check for running instance if feature is enabled - if (runningInstanceDetectionEnabled) - { - // Even if we fail to stop we won't block the apphost starting - // to make sure we don't ever break flow. It should mostly stop - // just fine though. - var runningInstanceResult = await project.FindAndStopRunningInstanceAsync(effectiveAppHostFile, ExecutionContext.HomeDirectory, cancellationToken); + // Check for running instance — even if we fail to stop we won't + // block the apphost starting to make sure we don't ever break flow. + // It should mostly stop just fine though. + var runningInstanceResult = await project.FindAndStopRunningInstanceAsync(effectiveAppHostFile, ExecutionContext.HomeDirectory, cancellationToken); - // If in isolated mode and a running instance was stopped, warn the user - if (isolated && runningInstanceResult == RunningInstanceResult.InstanceStopped) - { - InteractionService.DisplayMessage(KnownEmojis.Warning, RunCommandStrings.IsolatedModeRunningInstanceWarning); - } + // If in isolated mode and a running instance was stopped, warn the user + if (isolated && runningInstanceResult == RunningInstanceResult.InstanceStopped) + { + InteractionService.DisplayMessage(KnownEmojis.Warning, RunCommandStrings.IsolatedModeRunningInstanceWarning); } // The completion sources are the contract between RunCommand and IAppHostProject diff --git a/src/Aspire.Cli/DotNet/DotNetCliExecutionFactory.cs b/src/Aspire.Cli/DotNet/DotNetCliExecutionFactory.cs index 4055006d29d..fbd85db507e 100644 --- a/src/Aspire.Cli/DotNet/DotNetCliExecutionFactory.cs +++ b/src/Aspire.Cli/DotNet/DotNetCliExecutionFactory.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; -using Aspire.Cli.Configuration; using Aspire.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -14,7 +13,6 @@ namespace Aspire.Cli.DotNet; internal sealed class DotNetCliExecutionFactory( ILogger logger, IConfiguration configuration, - IFeatures features, CliExecutionContext executionContext) : IDotNetCliExecutionFactory { internal static int GetCurrentProcessId() => Environment.ProcessId; @@ -73,10 +71,7 @@ public IDotNetCliExecution CreateExecution(string[] args, IDictionary ComputeNuGetConfigHierarchySha256Async(DirectoryInfo w using var activity = telemetry.StartDiagnosticActivity(); string? rawKey = null; - bool cacheEnabled = useCache && features.IsFeatureEnabled(KnownFeatures.PackageSearchDiskCachingEnabled, defaultValue: true); + bool cacheEnabled = useCache; if (cacheEnabled) { try diff --git a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs index 36b20947242..95d88d5c198 100644 --- a/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs +++ b/src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; -using Aspire.Cli.Configuration; using Microsoft.Extensions.Configuration; using Semver; @@ -12,7 +11,7 @@ namespace Aspire.Cli.DotNet; /// /// Default implementation of that checks for dotnet on the system PATH. /// -internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration configuration) : IDotNetSdkInstaller +internal sealed class DotNetSdkInstaller(IConfiguration configuration) : IDotNetSdkInstaller { /// /// The minimum .NET SDK version required for Aspire. @@ -24,12 +23,6 @@ internal sealed class DotNetSdkInstaller(IFeatures features, IConfiguration conf { var minimumVersion = GetEffectiveMinimumSdkVersion(configuration); - if (!features.IsFeatureEnabled(KnownFeatures.MinimumSdkCheckEnabled, true)) - { - // If the feature is disabled, we assume the SDK is available - return (true, null, minimumVersion); - } - try { // Add --arch flag to ensure we only get SDKs that match the current architecture diff --git a/src/Aspire.Cli/KnownFeatures.cs b/src/Aspire.Cli/KnownFeatures.cs index 0531317439b..f478b7acd27 100644 --- a/src/Aspire.Cli/KnownFeatures.cs +++ b/src/Aspire.Cli/KnownFeatures.cs @@ -16,11 +16,8 @@ internal static class KnownFeatures { public static string FeaturePrefix => "features"; public static string UpdateNotificationsEnabled => "updateNotificationsEnabled"; - public static string MinimumSdkCheckEnabled => "minimumSdkCheckEnabled"; public static string ExecCommandEnabled => "execCommandEnabled"; - public static string OrphanDetectionWithTimestampEnabled => "orphanDetectionWithTimestampEnabled"; public static string ShowDeprecatedPackages => "showDeprecatedPackages"; - public static string PackageSearchDiskCachingEnabled => "packageSearchDiskCachingEnabled"; public static string StagingChannelEnabled => "stagingChannelEnabled"; public static string DefaultWatchEnabled => "defaultWatchEnabled"; public static string ShowAllTemplates => "showAllTemplates"; @@ -28,7 +25,6 @@ internal static class KnownFeatures public static string ExperimentalPolyglotJava => "experimentalPolyglot:java"; public static string ExperimentalPolyglotGo => "experimentalPolyglot:go"; public static string ExperimentalPolyglotPython => "experimentalPolyglot:python"; - public static string RunningInstanceDetectionEnabled => "runningInstanceDetectionEnabled"; private static readonly Dictionary s_featureMetadata = new() { @@ -37,31 +33,16 @@ internal static class KnownFeatures "Check if update notifications are disabled and set version check environment variable", DefaultValue: true), - [MinimumSdkCheckEnabled] = new( - MinimumSdkCheckEnabled, - "Enable or disable minimum .NET SDK version checking before running Aspire applications", - DefaultValue: true), - [ExecCommandEnabled] = new( ExecCommandEnabled, "Enable or disable the 'aspire exec' command for executing commands inside running resources", DefaultValue: false), - [OrphanDetectionWithTimestampEnabled] = new( - OrphanDetectionWithTimestampEnabled, - "Enable or disable timestamp-based orphan process detection to clean up stale Aspire processes", - DefaultValue: true), - [ShowDeprecatedPackages] = new( ShowDeprecatedPackages, "Show or hide deprecated packages in 'aspire add' search results", DefaultValue: false), - [PackageSearchDiskCachingEnabled] = new( - PackageSearchDiskCachingEnabled, - "Enable or disable disk caching for package search results to improve performance", - DefaultValue: true), - [StagingChannelEnabled] = new( StagingChannelEnabled, "Enable or disable access to the staging channel for early access to preview features and packages", @@ -95,12 +76,7 @@ internal static class KnownFeatures [ExperimentalPolyglotPython] = new( ExperimentalPolyglotPython, "Enable or disable experimental Python language support for polyglot Aspire applications", - DefaultValue: false), - - [RunningInstanceDetectionEnabled] = new( - RunningInstanceDetectionEnabled, - "Enable or disable detection of already running Aspire instances to prevent conflicts", - DefaultValue: true) + DefaultValue: false) }; /// diff --git a/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs index 11113158451..4300a9b1e24 100644 --- a/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs +++ b/tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs @@ -1268,21 +1268,6 @@ public Task UseOrFindAppHostProjectFileAsync(FileInf } } - [Fact] - public void RunCommand_RunningInstanceDetectionFeatureFlag_DefaultsToFalse() - { - // Verify that the running instance detection feature flag defaults to false - // to ensure existing behavior is not changed unless explicitly enabled - using var workspace = TemporaryWorkspace.Create(outputHelper); - var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper); - var provider = services.BuildServiceProvider(); - - var features = provider.GetRequiredService(); - var isEnabled = features.IsFeatureEnabled(KnownFeatures.RunningInstanceDetectionEnabled, defaultValue: true); - - Assert.True(isEnabled, "Running instance detection should be enabled by default"); - } - [Fact] public async Task RunCommand_WithNoBuildOption_SkipsBuildAndPassesNoBuildAndNoRestoreToRunner() { diff --git a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs index 1fc97211321..f7bf937ef07 100644 --- a/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs +++ b/tests/Aspire.Cli.Tests/DotNetSdkInstallerTests.cs @@ -16,7 +16,7 @@ public class DotNetSdkInstallerTests [Fact] public async Task CheckAsync_WhenDotNetIsAvailable_ReturnsTrue() { - var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), CreateEmptyConfiguration()); + var installer = new DotNetSdkInstaller(CreateEmptyConfiguration()); // This test assumes the test environment has .NET SDK installed var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -27,10 +27,8 @@ public async Task CheckAsync_WhenDotNetIsAvailable_ReturnsTrue() [Fact] public async Task CheckAsync_WithMinimumVersion_WhenDotNetIsAvailable_ReturnsTrue() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(features, configuration); + var installer = new DotNetSdkInstaller(configuration); // This test assumes the test environment has .NET SDK installed with a version >= 8.0.0 var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -41,10 +39,8 @@ public async Task CheckAsync_WithMinimumVersion_WhenDotNetIsAvailable_ReturnsTru [Fact] public async Task CheckAsync_WithActualMinimumVersion_BehavesCorrectly() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride(DotNetSdkInstaller.MinimumSdkVersion); - var installer = new DotNetSdkInstaller(features, configuration); + var installer = new DotNetSdkInstaller(configuration); // Use the actual minimum version constant and check the behavior var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -57,10 +53,8 @@ public async Task CheckAsync_WithActualMinimumVersion_BehavesCorrectly() [Fact] public async Task CheckAsync_WithHighMinimumVersion_ReturnsFalse() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("99.0.0"); - var installer = new DotNetSdkInstaller(features, configuration); + var installer = new DotNetSdkInstaller(configuration); // Use an unreasonably high version that should not exist var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -71,10 +65,8 @@ public async Task CheckAsync_WithHighMinimumVersion_ReturnsFalse() [Fact] public async Task CheckAsync_WithInvalidMinimumVersion_ReturnsFalse() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("invalid.version"); - var installer = new DotNetSdkInstaller(features, configuration); + var installer = new DotNetSdkInstaller(configuration); // Use an invalid version string var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -82,27 +74,11 @@ public async Task CheckAsync_WithInvalidMinimumVersion_ReturnsFalse() Assert.False(success); } - [Fact] - public async Task CheckReturnsTrueIfFeatureDisabled() - { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, false); - var configuration = CreateConfigurationWithOverride("invalid.version"); - var installer = new DotNetSdkInstaller(features, configuration); - - // Use an invalid version string - var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); - - Assert.True(success); - } - [Fact] public async Task CheckAsync_UsesArchitectureSpecificCommand() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(features, configuration); + var installer = new DotNetSdkInstaller(configuration); // This test verifies that the architecture-specific command is used // Since the implementation adds --arch flag, it should still work correctly @@ -116,7 +92,7 @@ public async Task CheckAsync_UsesArchitectureSpecificCommand() public async Task CheckAsync_UsesOverrideMinimumSdkVersion_WhenConfigured() { var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), configuration); + var installer = new DotNetSdkInstaller(configuration); // The installer should use the override version instead of the constant var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -128,7 +104,7 @@ public async Task CheckAsync_UsesOverrideMinimumSdkVersion_WhenConfigured() [Fact] public async Task CheckAsync_UsesDefaultMinimumSdkVersion_WhenNotConfigured() { - var installer = new DotNetSdkInstaller(new MinimumSdkCheckFeature(), CreateEmptyConfiguration()); + var installer = new DotNetSdkInstaller(CreateEmptyConfiguration()); // Call the parameterless method that should use the default constant var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -140,9 +116,7 @@ public async Task CheckAsync_UsesDefaultMinimumSdkVersion_WhenNotConfigured() [Fact] public async Task CheckAsync_UsesMinimumSdkVersion() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); - var installer = new DotNetSdkInstaller(features, CreateEmptyConfiguration()); + var installer = new DotNetSdkInstaller(CreateEmptyConfiguration()); // Call the parameterless method that should use the minimum SDK version var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -154,10 +128,8 @@ public async Task CheckAsync_UsesMinimumSdkVersion() [Fact] public async Task CheckAsync_UsesOverrideVersion_WhenOverrideConfigured() { - var features = new TestFeatures() - .SetFeature(KnownFeatures.MinimumSdkCheckEnabled, true); var configuration = CreateConfigurationWithOverride("8.0.0"); - var installer = new DotNetSdkInstaller(features, configuration); + var installer = new DotNetSdkInstaller(configuration); // The installer should use the override version instead of the baseline constant var (success, _, _) = await installer.CheckAsync().DefaultTimeout(); @@ -278,14 +250,6 @@ private static IConfiguration CreateConfigurationWithOverride(string overrideVer } } -public class MinimumSdkCheckFeature(bool enabled = true) : IFeatures -{ - public bool IsFeatureEnabled(string featureName, bool defaultValue = false) - { - return featureName == KnownFeatures.MinimumSdkCheckEnabled ? enabled : false; - } -} - public class TestFeatures : IFeatures { private readonly Dictionary _features = new(); From 8557a91a11f60e019f4782774e7f18c6c97c7ad0 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Mar 2026 08:57:43 +1100 Subject: [PATCH 2/3] Address review feedback: remove dead comment, simplify cache check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Cli/Commands/RunCommand.cs | 2 -- src/Aspire.Cli/DotNet/DotNetCliRunner.cs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Aspire.Cli/Commands/RunCommand.cs b/src/Aspire.Cli/Commands/RunCommand.cs index d2b9a4efa13..ed791e38c52 100644 --- a/src/Aspire.Cli/Commands/RunCommand.cs +++ b/src/Aspire.Cli/Commands/RunCommand.cs @@ -140,8 +140,6 @@ protected override async Task ExecuteAsync(ParseResult parseResult, Cancell Debug.Assert(_startDebugSessionOption is not null); startDebugSession = parseResult.GetValue(_startDebugSessionOption); } - // Force option kept for backward compatibility but no longer used since prompt was removed - // var force = parseResult.GetValue("--force"); // Validate that --format is only used with --detach if (format == OutputFormat.Json && !detach) diff --git a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs index c93b7a43754..573fd9982ef 100644 --- a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs +++ b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs @@ -919,8 +919,8 @@ public async Task ComputeNuGetConfigHierarchySha256Async(DirectoryInfo w using var activity = telemetry.StartDiagnosticActivity(); string? rawKey = null; - bool cacheEnabled = useCache; - if (cacheEnabled) + var cacheEnabled = useCache; + if (useCache) { try { From 01a8953971c93e4c031348819958a3eed06dcb2e Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 10 Mar 2026 09:18:13 +1100 Subject: [PATCH 3/3] Increase DeclineAgentInitPrompt timeout from 30s to 120s The 30-second timeout must cover both dotnet new project creation and the agent-init prompt appearing. In CI under load (e.g., KinD cluster running), dotnet new alone can exceed 30 seconds, causing a WaitUntilTimeoutException. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/Shared/Hex1bTestHelpers.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/Shared/Hex1bTestHelpers.cs b/tests/Shared/Hex1bTestHelpers.cs index 75795683204..13eac06dca7 100644 --- a/tests/Shared/Hex1bTestHelpers.cs +++ b/tests/Shared/Hex1bTestHelpers.cs @@ -279,14 +279,21 @@ internal static Hex1bTerminalInputSequenceBuilder ExecuteCallback( /// Handles the agent init confirmation prompt that appears after aspire init or aspire new. /// Declines the prompt so the command exits cleanly. /// + /// The sequence builder. + /// + /// How long to wait for the prompt. This must cover the time for project creation + /// (dotnet new) to finish plus the prompt appearing. Defaults to 120 seconds + /// to accommodate slow CI environments (e.g., when KinD clusters are running). + /// internal static Hex1bTerminalInputSequenceBuilder DeclineAgentInitPrompt( - this Hex1bTerminalInputSequenceBuilder builder) + this Hex1bTerminalInputSequenceBuilder builder, + TimeSpan? timeout = null) { var agentInitPrompt = new CellPatternSearcher() .Find("configure AI agent environments"); return builder - .WaitUntil(s => agentInitPrompt.Search(s).Count > 0, TimeSpan.FromSeconds(30)) + .WaitUntil(s => agentInitPrompt.Search(s).Count > 0, timeout ?? TimeSpan.FromSeconds(120)) .Wait(500) .Type("n") .Enter();