Skip to content

Avoid live CLI rendering when stdout is redirected - #19673

Merged
Ella Hathaway (ellahathaway) merged 5 commits into
microsoft:mainfrom
adamint:adamint-fix-remote-tty-crash
Sep 3, 2026
Merged

Ella Hathaway (ellahathaway) merged 5 commits into
microsoft:mainfrom
adamint:adamint-fix-remote-tty-crash

Conversation

@adamint

@adamint Adam Ratzman (adamint) commented Aug 25, 2026

Copy link
Copy Markdown
Member

Description

aspire run could enter Spectre live rendering in a Remote SSH session even when stdout was redirected. Resource endpoint updates then crashed the CLI with System.ArgumentException from LiveRenderable.Render, which also stopped the AppHost.

This change treats redirected stdout as non-interactive. Real terminals keep the existing Spectre live display, which replaces a cumulative endpoint snapshot in place. Redirected or otherwise non-interactive remote runs bypass DisplayLiveAsync: they print the CTRL+C hint once and append one line per newly observed endpoint, avoiding both cursor manipulation and duplicate snapshots. DisplayLiveAsync now rejects non-interactive calls so future callers must define appropriate static semantics.

User-facing usage

The reported path now stays running and writes plain endpoint updates:

TERM=dumb LINES=0 COLUMNS=80 \
VSCODE_IPC_HOOK_CLI=<socket> \
SSH_CONNECTION='<client> <port> <server> <port>' \
aspire run --project playground/DotnetProject/DotnetProject.AppHost/DotnetProject.AppHost.csproj \
  > stdout.log 2> stderr.log

Before this change, the command exited with code 2 when the first endpoint updated:

System.ArgumentException: Offset and length were out of bounds...
   at Spectre.Console.LiveRenderable.Render(...)
   at Aspire.Cli.Interaction.ConsoleInteractionService.DisplayLiveAsync(...)
   at Aspire.Cli.Commands.RunCommand.ProcessResourceState(...)

After this change, stdout contains one static record per endpoint transition with no carriage-return live redraws, stderr has no Spectre exception, and the AppHost remains running until stopped. The same Remote SSH command under a real PTY continues to use live rendering.

Screenshots / Recordings

This PR includes UI changes. Please add screenshots or screen recordings so reviewers can evaluate the visual changes without running locally.

  • For before/after comparisons, place them side-by-side or label them clearly.
  • For interactive changes (animations, transitions, new flows), prefer a short screen recording (GIF or video).
  • If you cannot capture visuals now, note what scenario to test and mark this section as TODO.

The affected surface is redirected stdout rather than a visual terminal layout, so the exact before/after command and output are included above. Interactive PTY rendering was verified separately and remains unchanged.

Validation

  • RunCommandTests, ConsoleInteractionServiceTests, and CliHostEnvironmentTests: 234 passed
  • SmokeTests.RedirectedRemoteSshRunUsesStaticOutput: 1 passed against a locally built Linux arm64 archive in 2m06s
  • dotnet build src/Aspire.Cli/Aspire.Cli.csproj --no-restore: 0 warnings, 0 errors
  • Redirected Remote SSH path: no carriage-return live redraws, no duplicate endpoint records, no exception, AppHost remained running
  • Exact Remote SSH path under a PTY: live endpoint updates remained enabled
  • Full four-lane review plus focused blocker re-review: no blocking findings remain

Resolves #19290

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

Adam Ratzman (adamint) and others added 2 commits August 25, 2026 14:20
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 25, 2026 18:54
@github-actions

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 -- 19673

Or

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

@adamint

Copy link
Copy Markdown
Member Author

Copilot review

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

Prevents Spectre live rendering when CLI stdout is redirected, avoiding Remote SSH crashes while preserving interactive terminal behavior.

Changes:

  • Detects redirected stdout as non-interactive.
  • Falls back to static rendering for live updates.
  • Adds focused unit coverage.
Show a summary per file
File Description
src/Aspire.Cli/Utils/CliHostEnvironment.cs Detects redirected stdout.
src/Aspire.Cli/Interaction/ConsoleInteractionService.cs Adds static rendering fallback.
tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs Tests redirected-output detection.
tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs Tests static live-update output.

Review details

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Utils/CliHostEnvironment.cs
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 25, 2026 19:52

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.

Review details

Suppressed comments (1)

src/Aspire.Cli/Utils/CliHostEnvironment.cs:96

  • isOutputRedirected is only checked in this final branch, so ASPIRE_PLAYGROUND=true still hard-codes SupportsInteractiveOutput to true. BuildAnsiConsole then forces InteractionSupport.Yes, meaning redirecting stdout in playground mode can still enter DisplayLiveAsync's Spectre live path and reproduce the same crash. Redirected output should take precedence for output capability (while playground may still force input/ANSI), with the playground tests updated to cover both redirected and terminal output.
            SupportsInteractiveOutput = DetectInteractiveOutput(configuration, isOutputRedirected);
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

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.

This fixes the error by making data no longer live. But it was live for a reason. What is the new behavior?

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 1, 2026 00:31

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.

Copilot review overview

Review tier: Balanced
Findings: None

@adamint

Copy link
Copy Markdown
Member Author

Good call. Interactive terminals still redraw endpoint updates in place with Spectre Live. Redirected/non-TTY output skips Live, but appends the initial state and each update so progress and the final snapshot remain in logs. I added coverage for both paths and made redirection win over the playground override.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf0deea0-a8ac-4680-a792-b0122657e423
Copilot AI review requested due to automatic review settings September 2, 2026 22:17

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.

Copilot review overview

🟢 Approval recommended

The implementation addresses the reported failure with focused unit and end-to-end regression coverage.

Review tier: Balanced
Findings: None

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@ellahathaway

Ella Hathaway (ellahathaway) commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Testing Report

PR Information

Artifact Version Verification

  • Expected Commit: cb20f0b1287004b54af6f9886a2c9b837413f1f4
  • Installed Version: 13.6.0-pr.19673.gcb20f0b1
  • Install Source: PR dogfood artifact from GitHub Actions run 33689651134
  • Status: Verified; the installed version contains the PR head short commit cb20f0b1

Changes Analyzed

Files Changed

  • src/Aspire.Cli/Commands/RunCommand.cs
  • src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
  • src/Aspire.Cli/Utils/CliHostEnvironment.cs
  • tests/Aspire.Cli.EndToEnd.Tests/SmokeTests.cs
  • tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs
  • tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs
  • tests/Aspire.Cli.Tests/Utils/CliHostEnvironmentTests.cs

Change Categories

  • CLI behavior and terminal rendering
  • Unit and CLI end-to-end tests
  • Hosting integration
  • Dashboard
  • Client/component
  • VS Code extension
  • CI infrastructure

Test Scenarios Executed

Scenario 1: Exact PR artifact installation and identity

Objective: Install the current PR dogfood CLI in an isolated Windows temp directory and prove it matches the latest PR commit.

Coverage Type: Artifact boundary

Status: Passed

Evidence:

  • Installed version: 13.6.0-pr.19673.gcb20f0b1
  • Expected head: cb20f0b1287004b54af6f9886a2c9b837413f1f4

Scenario 2: Fresh project creation from the PR hive

Objective: Verify the PR CLI and PR package hive can create a fresh starter application non-interactively.

Coverage Type: Happy path

Status: Passed

Observations:

  • Template resolution selected 13.6.0-pr.19673.gcb20f0b1.
  • Project creation completed successfully with Redis disabled and prompts suppressed.

Scenario 3: Redirected Remote SSH run

Objective: Reproduce the reported redirected Remote SSH boundary with playground mode forced so stdout redirection is the decisive non-interactive capability.

Coverage Type: Regression and unhappy-path boundary

Status: Passed

Observations:

  • aspire run remained alive after endpoint discovery.
  • aspire ps --format json reported the generated AppHost as running.
  • Four endpoint records were emitted and all four were unique.
  • The Ctrl+C hint appeared exactly once.
  • Output contained no Spectre Live cursor visibility controls.
  • Neither stream contained LiveRenderable, System.ArgumentException, or an unexpected-error signature.
  • aspire stop --apphost <path> stopped the AppHost and the original run process exited with code 0.

Scenario 4: Build and focused boundary tests

Objective: Verify the source-level redirected detection, explicit non-interactive live-rendering guard, incremental static path, and preserved interactive live path.

Coverage Type: Build, unit, and command integration

Status: Passed

Commands:

dotnet build src\Aspire.Cli\Aspire.Cli.csproj --no-restore

dotnet test --project tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj --no-launch-profile -- `
  --filter-class "*.RunCommandTests" `
  --filter-class "*.ConsoleInteractionServiceTests" `
  --filter-class "*.CliHostEnvironmentTests" `
  --filter-not-trait "quarantined=true" `
  --filter-not-trait "outerloop=true"

Results:

  • Build: 0 warnings, 0 errors.
  • Tests: 234 passed, 0 failed, 0 skipped.

Evidence

Sanitized evidence was retained locally and was not uploaded to GitHub.

  • version.txt
  • new-output.txt
  • remote-ssh.stdout
  • remote-ssh.stderr
  • ps.json
  • stop-output.txt
  • scenario-result.json

No screenshots were captured because the changed surface is redirected terminal output rather than dashboard UI.

Summary

Scenario Status Notes
Artifact install and identity Passed Exact PR head verified
Fresh project creation Passed PR template and hive
Redirected Remote SSH run Passed Four unique endpoints; clean stop
Build and boundary tests Passed 234 tests; clean build

Overall Result

PR #19673 verified successfully.

@eerhardt Eric Erhardt (eerhardt) left a comment

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.

LGTM

@ellahathaway
Ella Hathaway (ellahathaway) dismissed James Newton-King (JamesNK)’s stale review September 3, 2026 20:28

Comments have been addressed, and James is unavailable for a re-review

@ellahathaway
Ella Hathaway (ellahathaway) merged commit a7c95f8 into microsoft:main Sep 3, 2026
335 of 338 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.6 milestone Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

⚠️ CI Failure Analysis: Possible Flaky Test(s)

The CI build failed due to test failure(s) that appear unrelated to the PR changes. These may be flaky tests.

Suspected flaky test(s):

  • Aspire.Hosting.JavaScript.Tests.NodeFunctionalTests.VerifyNodeAppWorks in job Hosting.JavaScript (windows-latest)
    • Error: Class fixture type 'Aspire.Hosting.JavaScript.Tests.NodeAppFixture' threw in InitializeAsync
      ---- Polly.Timeout.TimeoutRejectedException : The operation didn't complete within the allowed timeout of '00:01:00'.
      -------- System.Threading.Tasks.TaskCanceledException : A task was canceled.
      ------------ System.Threading.Tasks.TaskCanceledException : A task was canceled.
    • Stack Trace (first frames):
      at Polly.Utils.ExceptionUtilities.TrySetStackTrace[T](T exception)
         at Polly.Timeout.TimeoutResilienceStrategy.ExecuteCore[TResult,TState](Func`3 callback, ResilienceContext context, TState state)
         at Polly.Utils.Pipeline.BridgeComponent`1.<ExecuteCore>g__ConvertValueTaskAsync|4_0[TResult,TState](ValueTask`1 valueTask, ResilienceContext resilienceContext)
         at Polly.Retry.RetryResilienceStrategy`1.ExecuteCore[TState](Func`3 callback, Resilien
      
    • Why likely flaky: Node app startup fixture timed out after 1 minute on a Windows runner; unrelated to PR changes (which only touch Aspire.Cli). Consistent with environmental slow-start/resource contention on shared CI runners.
  • Aspire.Hosting.JavaScript.Tests.DenoTelemetryFunctionalTests.VerifyDenoAppExportsNativeTelemetryToDashboard in job Hosting.JavaScript (windows-latest)
    • Error: Class fixture type 'Aspire.Hosting.JavaScript.Tests.DenoTelemetryFixture' threw in InitializeAsync
      ---- Polly.Timeout.TimeoutRejectedException : The operation didn't complete within the allowed timeout of '00:01:00'.
      -------- System.Threading.Tasks.TaskCanceledException : A task was canceled.
      ------------ System.Threading.Tasks.TaskCanceledException : A task was canceled.
    • Stack Trace (first frames):
      at Polly.Utils.ExceptionUtilities.TrySetStackTrace[T](T exception)
         at Polly.Timeout.TimeoutResilienceStrategy.ExecuteCore[TResult,TState](Func`3 callback, ResilienceContext context, TState state)
         at Polly.Utils.Pipeline.BridgeComponent`1.<ExecuteCore>g__ConvertValueTaskAsync|4_0[TResult,TState](ValueTask`1 valueTask, ResilienceContext resilienceContext)
         at Polly.Retry.RetryResilienceStrategy`1.ExecuteCore[TState](Func`3 callback, Resilien
      
    • Why likely flaky: Deno app startup fixture timed out after 1 minute on a Windows runner; unrelated to PR changes (which only touch Aspire.Cli). Consistent with environmental slow-start/resource contention on shared CI runners.
  • Aspire.Hosting.JavaScript.Tests.NodeFunctionalTests.VerifyNpmAppWorks in job Hosting.JavaScript (windows-latest)
    • Error: Class fixture type 'Aspire.Hosting.JavaScript.Tests.NodeAppFixture' threw in InitializeAsync
      ---- Polly.Timeout.TimeoutRejectedException : The operation didn't complete within the allowed timeout of '00:01:00'.
      -------- System.Threading.Tasks.TaskCanceledException : A task was canceled.
      ------------ System.Threading.Tasks.TaskCanceledException : A task was canceled.
    • Stack Trace (first frames):
      at Polly.Utils.ExceptionUtilities.TrySetStackTrace[T](T exception)
         at Polly.Timeout.TimeoutResilienceStrategy.ExecuteCore[TResult,TState](Func`3 callback, ResilienceContext context, TState state)
         at Polly.Utils.Pipeline.BridgeComponent`1.<ExecuteCore>g__ConvertValueTaskAsync|4_0[TResult,TState](ValueTask`1 valueTask, ResilienceContext resilienceContext)
         at Polly.Retry.RetryResilienceStrategy`1.ExecuteCore[TState](Func`3 callback, Resilien
      
    • Why likely flaky: Npm app startup fixture timed out after 1 minute on a Windows runner; unrelated to PR changes (which only touch Aspire.Cli). Consistent with environmental slow-start/resource contention on shared CI runners; all three JS/Node/Deno tests in the same job failed together with identical timeout symptoms, indicating a shared fixture/runner-level slowdown rather than test-specific bugs.

Suggested actions:

  • Re-run the failed CI jobs to confirm if the failure is intermittent
  • If the test continues to fail, consider quarantining it using /quarantine-test <test name> <issue URL>
  • Search existing issues to see if this test is already known to be flaky

You can re-run the failed jobs from the workflow run page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI crashes in Spectre.Console.LiveRenderable over Remote SSH without a TTY

5 participants