Skip to content

Fix ANSI leaks in plain logs and telemetry output#16225

Merged
sebastienros merged 7 commits intomainfrom
sebastienros/sebros-fix-plain-logs-ansi-output
Apr 17, 2026
Merged

Fix ANSI leaks in plain logs and telemetry output#16225
sebastienros merged 7 commits intomainfrom
sebastienros/sebros-fix-plain-logs-ansi-output

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

Description

Fixes the plain-text output leak reported in #15845. In non-ANSI and automation-oriented scenarios, the CLI could still emit Spectre styling or pass through embedded ANSI sequences, which showed up as raw fragments in aspire logs and telemetry output.

This change makes --non-interactive force plain console rendering, updates the CLI console setup to honor the resolved ANSI capability instead of falling back to auto-detection, disables hyperlinks when ANSI is off, and strips embedded control sequences from human-readable aspire logs and aspire otel logs output while preserving JSON output.

Validation:

  • ./dotnet.sh test tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj -- --filter-class "*.LogsCommandTests" --filter-class "*.TelemetryLogsCommandTests" --filter-class "*.TelemetryTracesCommandTests" --filter-class "*.CliHostEnvironmentTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"

Fixes #15845

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
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI review requested due to automatic review settings April 16, 2026 00:10
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros force-pushed the sebastienros/sebros-fix-plain-logs-ansi-output branch from f1b9697 to fab8032 Compare April 16, 2026 00:13
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

Fixes ANSI/styling “leaks” in plain-text CLI scenarios (notably aspire logs and aspire otel logs) by making non-interactive and non-ANSI outputs reliably control-sequence-free, while preserving JSON output for automation.

Changes:

  • Make --non-interactive force SupportsAnsi=false (and update related tests).
  • Configure Spectre.Console to default to plain rendering unless ANSI is explicitly enabled via resolved host capabilities; disable hyperlinks when ANSI is off.
  • Strip embedded ANSI/control sequences from human-readable logs/otel logs output when ANSI is disabled; add coverage to ensure JSON preserves raw content.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs Adds/updates tests to ensure non-interactive mode disables ANSI.
tests/Aspire.Cli.Tests/Commands/TelemetryLogsCommandTests.cs Adds test to ensure telemetry log body strips ANSI when ANSI is disabled.
tests/Aspire.Cli.Tests/Commands/LogsCommandTests.cs Adds tests for stripping ANSI in text output and preserving ANSI in JSON output; improves test injection of log lines.
src/Aspire.Cli/Utils/CliHostEnvironment.cs Makes --non-interactive explicitly disable ANSI support.
src/Aspire.Cli/Program.cs Adjusts Spectre console settings to honor resolved ANSI capability and disables hyperlink capability when ANSI is off.
src/Aspire.Cli/Commands/TelemetryLogsCommand.cs Strips embedded control sequences from log bodies when ANSI is disabled.
src/Aspire.Cli/Commands/LogsCommand.cs Strips embedded control sequences from log content when ANSI is disabled (while keeping JSON intact).

Comment thread src/Aspire.Cli/Program.cs
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 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 -- 16225

Or

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

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

I agree with the change to strip ANSI from external sources (logs) when not supported, but why should --non-interactive force no ANSI?

If an environment doesn't support ANSI then existing built-in detection should turn it off.

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

I agree with the change to strip ANSI from external sources (logs) when not supported, but why should --non-interactive force no ANSI?

If an environment doesn't support ANSI then existing built-in detection should turn it off.

Because we also use --non-interactive as an explicit automation/agent mode even when stdout is still attached to a terminal that can render ANSI. In that case the built-in detection would still allow CLI-owned colors and OSC 8 links, which is what caused the plain-text mismatch with the rest of the non-interactive contract.

The log-payload stripping is independent of that. This part just keeps the CLI's own formatting behavior aligned with --non-interactive, so the command stays plain even when it is being driven from an ANSI-capable terminal.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

But non-interactive is just saying no interaction please. It shouldn't control what the output is.

If stdout is redirected to a then ANSI is disabled. And if a terminal doesn't support ANSI then it would use the standard NO_COLOR env var when it runs the process to suppress ANSI.

I think there are lots of options already. Non-interactive doesn't need to control more than it needs to.

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

But non-interactive is just saying no interaction please. It shouldn't control what the output is.

If stdout is redirected to a then ANSI is disabled. And if a terminal doesn't support ANSI then it would use the standard NO_COLOR env var when it runs the process to suppress ANSI.

I think there are lots of options already. Non-interactive doesn't need to control more than it needs to.

Agreed. I updated the branch so --non-interactive only disables interaction again. ANSI now goes back to the normal host-based behavior (NO_COLOR, pass-through, redirection, terminal capability), while the fix here stays focused on the actual leak paths: honoring the resolved host ANSI capability in the console setup and stripping embedded ANSI from plain log output when ANSI is off.

Comment thread src/Aspire.Cli/Program.cs Outdated
Comment thread src/Aspire.Cli/Program.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

Clean refactoring and good test coverage for the ANSI stripping in plain-text output. Two items flagged: one potential unintended color downgrade in playground mode, one test robustness suggestion.

Comment thread src/Aspire.Cli/Program.cs
Comment thread tests/Aspire.Cli.Tests/Commands/LogsCommandTests.cs
sebastienros and others added 2 commits April 15, 2026 19:12
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread tests/Aspire.Cli.Tests/ProgramTests.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros enabled auto-merge (squash) April 17, 2026 00:09
@sebastienros sebastienros merged commit ec96054 into main Apr 17, 2026
281 checks passed
@sebastienros sebastienros deleted the sebastienros/sebros-fix-plain-logs-ansi-output branch April 17, 2026 00:32
@github-actions github-actions Bot added this to the 13.3 milestone Apr 17, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 72 recordings uploaded (commit c9509ea)

View recordings
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 ▶️ 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
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ 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
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ 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
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24540519621

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.

Plain aspire logs / telemetry output leaks ANSI fragments

3 participants