Skip to content

Fix aspire start --format json restart output#16228

Merged
sebastienros merged 5 commits intomainfrom
sebastienros/fix-json-start-output
Apr 16, 2026
Merged

Fix aspire start --format json restart output#16228
sebastienros merged 5 commits intomainfrom
sebastienros/fix-json-start-output

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

@sebastienros sebastienros commented Apr 16, 2026

Description

Adds restart-path coverage for the existing aspire start --format json contract: stdout should remain a single JSON document even when an existing AppHost instance is restarted.

The PR also updates MultipleAppHostTests to use CliInstallStrategy.Detect() so local archive runs exercise the current branch, and it tightens the existing detached JSON validation to assert the expected keys instead of only printing booleans.

Related to #15843

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?

Suppress human-readable restart and status output when \ runs, and add an end-to-end regression that validates combined output stays valid JSON during restart.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 16, 2026 00:23
@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 -- 16228

Or

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

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

Isn't this already handled today by outputting human readable content to stderr and JSON to stdout? That's a common pattern in CLI apps when outputing structured data.

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

Note

Copilot was unable to run its full agentic suite in this review.

Fixes aspire start --format json (detached/restart path) so machine-readable JSON output isn’t polluted by human-readable restart/stop/status messages, and adds an end-to-end regression test to prevent regressions.

Changes:

  • Suppress human-readable “stopping previous instance” / “stopped successfully” messaging when JSON output is requested during detached launch/restart.
  • Add an E2E regression that validates combined stdout/stderr remains valid JSON on restart.
  • Update E2E test setup to use the install strategy flow for Docker runs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs Adds an E2E restart regression test and updates install flow usage.
src/Aspire.Cli/Projects/RunningInstanceManager.cs Adds a switch to suppress human-readable stop progress/success messaging.
src/Aspire.Cli/Commands/AppHostLauncher.cs Threads JSON-mode suppression into the detached launcher stop/restart flow.

Comment thread tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs Outdated
Comment thread tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs Outdated
Update the restart-path E2E test to capture combined output without a tee pipeline, and rely on validating the parsed JSON shape instead of brittle English phrase checks.\n\nCo-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.

Isn't this already handled today by outputting human readable content to stderr and JSON to stdout? That's a common pattern in CLI apps when outputing structured data.

@sebastienros
Copy link
Copy Markdown
Contributor Author

Isn't this already handled today by outputting human readable content to stderr and JSON to stdout? That's a common pattern in CLI apps when outputing structured data.

The stdout/stderr split is already there, but the issue in #15843 is that the detached restart path was still emitting human-readable stop/status lines before the final JSON payload. In practice that meant the combined command output shown in the issue was not valid JSON:

aspire start --format json
Stopping previous instance ...
Running instance stopped successfully.
{ ... }

That is a fine pattern when callers only consume stdout, but aspire start --format json currently returns a single JSON document and the auto-stop portion does not have its own structured output (aspire stop does not support --format json). So there is no machine-readable way for a caller to parse the restart path today if we keep the extra human text.

The change is scoped to that JSON detached-start path only. Normal start/stop behavior stays human-readable, and non-JSON flows still use the existing stderr/stdout behavior.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

So there is no machine-readable way for a caller to parse the restart path today if we keep the extra human text.

The solution is to put the extra human text in stderr, no?

I setup a system that as soon as --format json is present in the command line then all interaction service calls to display a message, or display an interaction, always use stderr. It's a global setting.

Is the problem that aspire start --format json is getting redirected output from the child process as stdout and stderr and putting it all to stdout?

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

I tested this scenario on main adn it seems to work how I would have expected. Only JSON is in stdout:

image

@sebastienros
Copy link
Copy Markdown
Contributor Author

I tested this scenario on main adn it seems to work how I would have expected. Only JSON is in stdout:

image

Right - stdout-only already behaves that way, and that matches the global stderr routing you added. The case this PR is fixing is narrower: the detached restart path still surfaced extra human-readable stop/status text in the overall command output, which is what the issue screenshot shows.

That's why the regression here validates the combined output with > combined-output.json 2>&1 for DetachFormatJsonProducesValidCombinedOutputWhenRestartingExistingInstance. On main, that restart-path combined output included the restart chatter before the JSON payload; after this change, the same combined stream is a single JSON document.

So this PR is not changing the existing stdout behavior. It's making the restart path consistent with the issue's expectation that aspire start --format json should be machine-readable even when a caller captures the full command output.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

I ran it again with a resource already running (that's what you mean by the restart path?) and still only got JSON in stdout:

image

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

Although, I'm using 13.2. Let me retry with CLI daily build.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

With daily CLI I still see only JSON in stdout when restarting:

image

@sebastienros
Copy link
Copy Markdown
Contributor Author

With daily CLI I still see only JSON in stdout when restarting:

image

Yep - that matches what I'd expect too. This PR is not trying to change stdout-only behavior. The issue is about the human-readable restart messages still being shown alongside the JSON result when aspire start --format json is run in a terminal, because those messages are emitted through the interaction service and still show up on stderr.

That's the distinction behind the regression: it validates the restart path when the caller captures the full command output with 2>&1, which mirrors what users see in the issue report and what many shells/scripts do when they collect a command's output.

So if the question is "does stdout already stay JSON-only on main?" then yes. If the question is "can aspire start --format json still surface human-readable restart chatter in the overall visible output?" that's the case this PR addresses.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented Apr 16, 2026

In that case I think the premise of the PR is wrong. It's common practice in CLI apps to display human readable messages to stderr and structured content to stdout. This is what Aspire CLI already does.

See https://chatgpt.com/share/69e042f8-57cc-8321-b76c-23a947a93f37

Revert the stderr-suppression behavior so detached start keeps the existing CLI contract of structured output on stdout and human-readable progress on stderr. Replace the restart-path regression with a stdout-only JSON assertion that covers restarting an existing instance.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros
Copy link
Copy Markdown
Contributor Author

In that case I think the premise of the PR is wrong. It's common practice in CLI apps to display human readable messages to stderr and structured content to stdout. This is what Aspire CLI already does.

See https://chatgpt.com/share/69e042f8-57cc-8321-b76c-23a947a93f37

Agreed, and I've updated the branch to match that contract. The latest push removes the stderr-suppression behavior and narrows the change to a restart-path regression: aspire start --format json keeps human-readable progress on stderr, while stdout remains a single JSON document even when an existing instance is restarted.

So the PR is now about covering the existing stdout contract in the restart path rather than changing overall terminal-visible output semantics.

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.

LGTM — the new test correctly validates the restart-path JSON contract, and the API migration to CliInstallStrategy.Detect() is clean. Two minor observations posted as comments (stale PR description, pre-existing weak validation in the first test).

Comment thread tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs
Comment thread tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs
Strengthen the existing detached JSON E2E validation to assert required keys and explicitly wait for the success marker, matching the restart-path regression.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs
Comment thread tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs
Switch the restart-path MultipleAppHostTests setup and cleanup to AspireStartAsync/AspireStopAsync so the test follows the shared end-to-end helper patterns.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

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

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 #24490202101

@sebastienros sebastienros merged commit 34677cd into main Apr 16, 2026
281 checks passed
@sebastienros sebastienros deleted the sebastienros/fix-json-start-output branch April 16, 2026 03:47
@github-actions github-actions Bot added this to the 13.3 milestone Apr 16, 2026
@aspire-repo-bot
Copy link
Copy Markdown
Contributor

No documentation PR is required for this change.

Reason: This PR is a bug fix that only modifies test files (tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs). It adds test coverage for the existing aspire start --format json restart behavior but introduces no new public APIs, configuration options, or user-facing features. The existing documentation already covers the --format json behavior adequately.

Generated by PR Documentation Check for issue #16228 · ● 131.4K ·

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.

4 participants