From ace261d68b5c7ceaaad42b14a97e48b489cfe316 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Mar 2026 14:20:33 +1100 Subject: [PATCH 1/2] chore: trigger CI build for issue #14819 investigation Whitespace change to trigger CI pipeline and E2E test execution for reproducing the flaky DetachFormatJsonProducesValidJson test. Fixes #14819 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs index b7b3ee942af..89b7f87a936 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/MultipleAppHostTests.cs @@ -130,3 +130,4 @@ public async Task DetachFormatJsonProducesValidJson() await pendingRun; } } + From 7ff3e555ce87cd640fdc989b35f36843383bfe97 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Mar 2026 14:40:54 +1100 Subject: [PATCH 2/2] Fix flaky E2E test: retry MSBuild evaluation on empty output When dotnet msbuild -getProperty/-getItem returns exit code 0 but produces no stdout output (likely due to MSBuild server contention when another AppHost process is running), JsonDocument.Parse would throw 'The input does not contain any JSON tokens', crashing the aspire run --detach --format json command. This fix adds retry logic (up to 3 attempts with increasing delay) for this specific case, making the project discovery more resilient to concurrent MSBuild operations. Fixes #14819 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Aspire.Cli/DotNet/DotNetCliRunner.cs | 98 +++++++++++++++--------- 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs index 9514676f717..a4705c162f3 100644 --- a/src/Aspire.Cli/DotNet/DotNetCliRunner.cs +++ b/src/Aspire.Cli/DotNet/DotNetCliRunner.cs @@ -339,48 +339,76 @@ private async Task StartBackchannelAsync(IDotNetCliExecution? execution, string string[] cliArgs = [.. cliArgsList]; - var stdoutBuilder = new StringBuilder(); - var existingStandardOutputCallback = options.StandardOutputCallback; // Preserve the existing callback if it exists. - options.StandardOutputCallback = (line) => { - stdoutBuilder.AppendLine(line); - existingStandardOutputCallback?.Invoke(line); - }; + var existingStandardOutputCallback = options.StandardOutputCallback; + var existingStandardErrorCallback = options.StandardErrorCallback; - var stderrBuilder = new StringBuilder(); - var existingStandardErrorCallback = options.StandardErrorCallback; // Preserve the existing callback if it exists. - options.StandardErrorCallback = (line) => { - stderrBuilder.AppendLine(line); - existingStandardErrorCallback?.Invoke(line); - }; + // Retry when MSBuild returns success but produces no output, which can happen + // due to MSBuild server contention (e.g. when another AppHost build is running). + const int maxRetries = 3; + for (var attempt = 0; attempt < maxRetries; attempt++) + { + var stdoutBuilder = new StringBuilder(); + options.StandardOutputCallback = (line) => { + stdoutBuilder.AppendLine(line); + existingStandardOutputCallback?.Invoke(line); + }; - var exitCode = await ExecuteAsync( - args: cliArgs, - env: null, - projectFile: projectFile, - workingDirectory: projectFile.Directory!, - backchannelCompletionSource: null, - options: options, - cancellationToken: cancellationToken); + var stderrBuilder = new StringBuilder(); + options.StandardErrorCallback = (line) => { + stderrBuilder.AppendLine(line); + existingStandardErrorCallback?.Invoke(line); + }; - var stdout = stdoutBuilder.ToString(); - var stderr = stderrBuilder.ToString(); + var exitCode = await ExecuteAsync( + args: cliArgs, + env: null, + projectFile: projectFile, + workingDirectory: projectFile.Directory!, + backchannelCompletionSource: null, + options: options, + cancellationToken: cancellationToken); - if (exitCode != 0) - { - logger.LogError( - "Failed to get items and properties from project. Exit code was: {ExitCode}. See debug logs for more details. Stderr: {Stderr}, Stdout: {Stdout}", - exitCode, - stderr, - stdout - ); + var stdout = stdoutBuilder.ToString(); + var stderr = stderrBuilder.ToString(); - return (exitCode, null); - } - else - { - var json = JsonDocument.Parse(stdout!); + if (exitCode != 0) + { + logger.LogError( + "Failed to get items and properties from project. Exit code was: {ExitCode}. See debug logs for more details. Stderr: {Stderr}, Stdout: {Stdout}", + exitCode, + stderr, + stdout + ); + + return (exitCode, null); + } + + if (string.IsNullOrWhiteSpace(stdout)) + { + if (attempt < maxRetries - 1) + { + logger.LogWarning( + "dotnet msbuild returned exit code 0 but produced no output (attempt {Attempt}/{MaxRetries}). Retrying after delay. Stderr: {Stderr}", + attempt + 1, + maxRetries, + stderr); + await Task.Delay(TimeSpan.FromSeconds(attempt + 1), cancellationToken).ConfigureAwait(false); + continue; + } + + logger.LogWarning( + "dotnet msbuild returned exit code 0 but produced no output after {MaxRetries} attempts. Stderr: {Stderr}", + maxRetries, + stderr); + return (exitCode, null); + } + + var json = JsonDocument.Parse(stdout); return (exitCode, json); } + + // Should not be reached, but return failure as a safety net + return (1, null); } public async Task RunAsync(FileInfo projectFile, bool watch, bool noBuild, bool noRestore, string[] args, IDictionary? env, TaskCompletionSource? backchannelCompletionSource, DotNetCliRunnerInvocationOptions options, CancellationToken cancellationToken)