From 679346ff94b856c745861babbe9fadb4c335c3e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:41:24 +0000 Subject: [PATCH 1/8] Initial plan From d0c9e0cd38bb0144f2e08f0210dc2633e4dd8173 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:46:16 +0000 Subject: [PATCH 2/8] Add AzDO issue prefixes for JobMonitor warning/error logs --- .../JobMonitor/JobMonitorRunner.cs | 38 +++++++++---------- .../JobMonitor/Program.cs | 9 ++++- .../JobMonitorRunnerTests.cs | 5 +++ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index 68bc4bf20f7..20e893c851b 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -16,6 +16,9 @@ namespace Microsoft.DotNet.Helix.JobMonitor { internal sealed class JobMonitorRunner : IJobMonitorRunner, IDisposable { + private const string AzdoWarningPrefix = "##vso[task.logissue type=warning]"; + private const string AzdoErrorPrefix = "##vso[task.logissue type=error]"; + private readonly JobMonitorOptions _options; private readonly ILogger _logger; private readonly IAzureDevOpsService _azdo; @@ -171,7 +174,7 @@ private async Task CancelInFlightHelixJobsAsync( return; } - _logger.LogWarning("Cancellation requested. Attempting to cancel {Count} in-flight Helix job(s).", inFlightJobs.Count); + LogWarning($"Cancellation requested. Attempting to cancel {inFlightJobs.Count} in-flight Helix job(s)."); Task[] cancelTasks = [ @@ -189,7 +192,7 @@ private async Task CancelInFlightHelixJobsAsync( } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to cancel Helix job {JobName}.", job.DisplayName); + LogWarning(ex, $"Failed to cancel Helix job {job.DisplayName}."); } }) ]; @@ -310,7 +313,7 @@ private async Task RunLoopAsync( if (anyNonMonitorJobFailures) { - _logger.LogError("One or more non-monitor pipeline jobs failed."); + LogError("One or more non-monitor pipeline jobs failed."); return 1; } @@ -697,12 +700,7 @@ private void LogFailedWorkItemConsoleLinks(HelixJobInfo helixJob, IReadOnlyColle continue; } - _logger.LogInformation("❌ Work item '{WorkItemName}' in job '{JobName}' failed ({State}).{nl}Console: {ConsoleOutputUri}", - workItem.Name, - helixJob.DisplayName, - FormatWorkItemState(workItem), - Environment.NewLine, - GetConsoleOutputText(workItem.ConsoleOutputUri)); + LogWarning($"❌ Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); } } @@ -813,11 +811,7 @@ private void LogFinalFailedWorkItemConsoleInfo() lines.Add($"{(i == failures.Count - 1 ? " " : "│ ")}└─ Console: {failure.ConsoleOutput}"); } - _logger.LogError("❌ Failed work item console logs:{nl}Test results: {TestResultsUri}{nl}{FailedWorkItemConsoleLogs}", - Environment.NewLine, - GetTestResultsUri(), - Environment.NewLine, - string.Join(Environment.NewLine, lines)); + LogError($"❌ Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); } private string GetTestResultsUri() @@ -970,14 +964,18 @@ private void ReportTimeout( return; } - _logger.LogError( - $"Helix Job Monitor timed out after {{TimeoutMinutes}} minute(s) ({{Timeout}}). {{UnfinishedCount}} Helix job(s) had not finished:{Environment.NewLine}- {{UnfinishedJobs}}{Environment.NewLine}", - timeout.TotalMinutes, - timeout, - unfinishedJobs.Count, - string.Join(Environment.NewLine + "- ", unfinishedJobs.Select(j => $"{j.DetailsUri} ({j.QueueId})"))); + LogError($"Helix Job Monitor timed out after {timeout.TotalMinutes} minute(s) ({timeout}). {unfinishedJobs.Count} Helix job(s) had not finished:{Environment.NewLine}- {string.Join(Environment.NewLine + "- ", unfinishedJobs.Select(j => $"{j.DetailsUri} ({j.QueueId})"))}{Environment.NewLine}"); } + private void LogWarning(string message) + => _logger.LogWarning("{Prefix}{Message}", AzdoWarningPrefix, message); + + private void LogWarning(Exception exception, string message) + => _logger.LogWarning(exception, "{Prefix}{Message}", AzdoWarningPrefix, message); + + private void LogError(string message) + => _logger.LogError("{Prefix}{Message}", AzdoErrorPrefix, message); + public void Dispose() { (_azdo as IDisposable)?.Dispose(); diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/Program.cs b/src/Microsoft.DotNet.Helix/JobMonitor/Program.cs index 5d596462be7..0843a8b3d3b 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/Program.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/Program.cs @@ -12,6 +12,8 @@ namespace Microsoft.DotNet.Helix.JobMonitor { internal static class Program { + private const string AzdoErrorPrefix = "##vso[task.logissue type=error]"; + public static async Task Main(string[] args) { JobMonitorOptions options = null; @@ -23,7 +25,7 @@ public static async Task Main(string[] args) { using ILoggerFactory errorLoggerFactory = CreateLoggerFactory(verbose: false); ILogger errorLogger = errorLoggerFactory.CreateLogger(); - errorLogger.LogError(ex, "Helix Job Monitor terminated with an unhandled exception."); + LogError(errorLogger, ex, "Helix Job Monitor terminated with an unhandled exception."); return 1; } @@ -91,7 +93,7 @@ public static async Task Main(string[] args) } catch (Exception ex) { - logger.LogError(ex, "Helix Job Monitor terminated with an unhandled exception."); + LogError(logger, ex, "Helix Job Monitor terminated with an unhandled exception."); return 1; } } @@ -106,5 +108,8 @@ private static ILoggerFactory CreateLoggerFactory(bool verbose) .AddConsoleFormatter(); }); } + + private static void LogError(ILogger logger, Exception exception, string message) + => logger.LogError(exception, "{Prefix}{Message}", AzdoErrorPrefix, message); } } diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index eb5cfe0021f..5bbd506f172 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -1319,6 +1319,7 @@ public async Task MonitorTimesOut_DoesNotReportOrCancelJobsThatFinishedAfterFirs // cached snapshot was overwritten with the latest (finished) state. string timeoutMessage = Assert.Single(logger.Messages, m => m.Contains("Helix Job Monitor timed out", StringComparison.Ordinal)); + Assert.StartsWith("##vso[task.logissue type=error]", timeoutMessage, StringComparison.Ordinal); Assert.Contains("helix-stuck", timeoutMessage, StringComparison.Ordinal); Assert.DoesNotContain("helix-good", timeoutMessage, StringComparison.Ordinal); @@ -2630,11 +2631,15 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() && message.Contains("https://helix.example/wi-pass/console", StringComparison.Ordinal)); Assert.Contains(logger.Messages, message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, message => + message.Contains("##vso[task.logissue type=warning]❌ Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); Assert.Contains(logger.Messages, message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); + Assert.Contains(logger.Messages, message => + message.Contains("##vso[task.logissue type=error]❌ Failed work item console logs:", StringComparison.Ordinal)); Assert.DoesNotContain(logger.Messages, message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); } From baf3dd6c149580f12d37ac9633f4019a45711355 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:48:52 +0000 Subject: [PATCH 3/8] Add error helper overload for JobMonitor runner logging --- src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index 20e893c851b..e34e758d7dc 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -976,6 +976,9 @@ private void LogWarning(Exception exception, string message) private void LogError(string message) => _logger.LogError("{Prefix}{Message}", AzdoErrorPrefix, message); + private void LogError(Exception exception, string message) + => _logger.LogError(exception, "{Prefix}{Message}", AzdoErrorPrefix, message); + public void Dispose() { (_azdo as IDisposable)?.Dispose(); From 50bfbe655219524ef3ac32389c86fc5753617925 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:06:33 +0000 Subject: [PATCH 4/8] Remove emoji icons from JobMonitor log statements --- .../JobMonitor/JobMonitorRunner.cs | 17 ++++++++--------- .../JobMonitorRunnerTests.cs | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index e929c638a41..d5a8a832066 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -185,7 +185,7 @@ private async Task CancelInFlightHelixJobsAsync( try { await _helix.CancelJobAsync(job.JobName, cancellationToken); - _logger.LogInformation("🛑 Requested cancellation of Helix job {JobName}.{nl}{JobUri}", + _logger.LogInformation("Requested cancellation of Helix job {JobName}.{nl}{JobUri}", job.DisplayName, Environment.NewLine, job.DetailsUri); } catch (OperationCanceledException) @@ -304,7 +304,7 @@ private async Task RunLoopAsync( int totalWorkItems = _workItemOutcomes.Count; int failedWorkItems = _workItemOutcomes.Values.Count(passed => !passed); _logger.LogInformation( - "📊 Final summary:{nl}" + "Final summary:{nl}" + " Jobs: {TotalJobs} submitted / {ResubmittedJobs} resubmitted / {ProcessedJobs} processed{nl}" + " Work items: {TotalWorkItems} submitted / {ResubmittedWorkItems} resubmitted / {FailedWorkItems} failed", Environment.NewLine, @@ -373,7 +373,7 @@ private async Task LogHelixJobStatusAsync( JobWorkItemStatusCounts counts = GetStatusCounts(orderedJobs, workItemsByJob, completedJobNames, processedJobNames); _logger.LogInformation( - "ℹ️ Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + "Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + " {ProcessedWorkItems} processed / {CompletedWorkItems} completed / {RunningWorkItems} running / {WaitingWorkItems} waiting work items", counts.ProcessedJobs, counts.CompletedJobs, @@ -478,7 +478,7 @@ private static void AddVerboseJobLines( { string jobConnector = isLastJob ? "└─" : "├─"; string childPrefix = isLastJob ? " " : "│ "; - lines.Add($"{jobConnector} 🧪 Helix job {job.DisplayName} [{jobStatus}]"); + lines.Add($"{jobConnector} Helix job {job.DisplayName} [{jobStatus}]"); List orderedWorkItems = [ @@ -584,8 +584,7 @@ private async Task ProcessCompletedJobAsync( QueueTestResultUpload(helixJob, workItems, cancellationToken); } - _logger.LogInformation("{Icon} Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", - failedWorkItemCount == 0 ? "✅" : "❌", + _logger.LogInformation("Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", helixJob.DisplayName, failedWorkItemCount == 0 ? "succeeded" : "failed", successfulWorkItemCount, @@ -705,7 +704,7 @@ private void LogFailedWorkItemConsoleLinks(HelixJobInfo helixJob, IReadOnlyColle continue; } - LogWarning($"❌ Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); + LogWarning($"Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); } } @@ -816,7 +815,7 @@ private void LogFinalFailedWorkItemConsoleInfo() lines.Add($"{(i == failures.Count - 1 ? " " : "│ ")}└─ Console: {failure.ConsoleOutput}"); } - LogError($"❌ Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); + LogError($"Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); } private string GetTestResultsUri() @@ -827,7 +826,7 @@ private static string GetConsoleOutputText(string consoleOutputUri) private async Task ResubmitFailedJobsAsync(CancellationToken cancellationToken) { - _logger.LogInformation("🔁 Checking for failed Helix jobs to resubmit the failed work items..."); + _logger.LogInformation("Checking for failed Helix jobs to resubmit the failed work items..."); var retryingHelixSubmitterJobs = new HashSet(StringComparer.OrdinalIgnoreCase); var resubmittedJobs = new List(); diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index 323db9e3f31..bcdcc564676 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -2640,14 +2640,14 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=warning]❌ Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=warning]Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=error]❌ Failed work item console logs:", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=error]Failed work item console logs:", StringComparison.Ordinal)); logger.Messages.Should().NotContain(message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); } @@ -2733,7 +2733,7 @@ public async Task LoopStatus_VerboseLogsFullHelixJobWorkItemList() exitCode.Should().Be(1); logger.Messages.Should().Contain(message => message.Contains("Helix job details:", StringComparison.Ordinal) - && message.Contains("└─ 🧪 Helix job helix-linux [Running]", StringComparison.Ordinal) + && message.Contains("└─ Helix job helix-linux [Running]", StringComparison.Ordinal) && message.Contains(" ├─ wi-01 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-10 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-11 (Running)", StringComparison.Ordinal) From 0879cfbae8f3ff6933d219cdf61dafb5d58279e0 Mon Sep 17 00:00:00 2001 From: Premek Vysoky Date: Wed, 3 Jun 2026 13:08:26 +0200 Subject: [PATCH 5/8] Revert "Remove emoji icons from JobMonitor log statements" This reverts commit 50bfbe655219524ef3ac32389c86fc5753617925. --- .../JobMonitor/JobMonitorRunner.cs | 17 +++++++++-------- .../JobMonitorRunnerTests.cs | 6 +++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index d5a8a832066..e929c638a41 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -185,7 +185,7 @@ private async Task CancelInFlightHelixJobsAsync( try { await _helix.CancelJobAsync(job.JobName, cancellationToken); - _logger.LogInformation("Requested cancellation of Helix job {JobName}.{nl}{JobUri}", + _logger.LogInformation("🛑 Requested cancellation of Helix job {JobName}.{nl}{JobUri}", job.DisplayName, Environment.NewLine, job.DetailsUri); } catch (OperationCanceledException) @@ -304,7 +304,7 @@ private async Task RunLoopAsync( int totalWorkItems = _workItemOutcomes.Count; int failedWorkItems = _workItemOutcomes.Values.Count(passed => !passed); _logger.LogInformation( - "Final summary:{nl}" + "📊 Final summary:{nl}" + " Jobs: {TotalJobs} submitted / {ResubmittedJobs} resubmitted / {ProcessedJobs} processed{nl}" + " Work items: {TotalWorkItems} submitted / {ResubmittedWorkItems} resubmitted / {FailedWorkItems} failed", Environment.NewLine, @@ -373,7 +373,7 @@ private async Task LogHelixJobStatusAsync( JobWorkItemStatusCounts counts = GetStatusCounts(orderedJobs, workItemsByJob, completedJobNames, processedJobNames); _logger.LogInformation( - "Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + "ℹ️ Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + " {ProcessedWorkItems} processed / {CompletedWorkItems} completed / {RunningWorkItems} running / {WaitingWorkItems} waiting work items", counts.ProcessedJobs, counts.CompletedJobs, @@ -478,7 +478,7 @@ private static void AddVerboseJobLines( { string jobConnector = isLastJob ? "└─" : "├─"; string childPrefix = isLastJob ? " " : "│ "; - lines.Add($"{jobConnector} Helix job {job.DisplayName} [{jobStatus}]"); + lines.Add($"{jobConnector} 🧪 Helix job {job.DisplayName} [{jobStatus}]"); List orderedWorkItems = [ @@ -584,7 +584,8 @@ private async Task ProcessCompletedJobAsync( QueueTestResultUpload(helixJob, workItems, cancellationToken); } - _logger.LogInformation("Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", + _logger.LogInformation("{Icon} Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", + failedWorkItemCount == 0 ? "✅" : "❌", helixJob.DisplayName, failedWorkItemCount == 0 ? "succeeded" : "failed", successfulWorkItemCount, @@ -704,7 +705,7 @@ private void LogFailedWorkItemConsoleLinks(HelixJobInfo helixJob, IReadOnlyColle continue; } - LogWarning($"Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); + LogWarning($"❌ Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); } } @@ -815,7 +816,7 @@ private void LogFinalFailedWorkItemConsoleInfo() lines.Add($"{(i == failures.Count - 1 ? " " : "│ ")}└─ Console: {failure.ConsoleOutput}"); } - LogError($"Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); + LogError($"❌ Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); } private string GetTestResultsUri() @@ -826,7 +827,7 @@ private static string GetConsoleOutputText(string consoleOutputUri) private async Task ResubmitFailedJobsAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Checking for failed Helix jobs to resubmit the failed work items..."); + _logger.LogInformation("🔁 Checking for failed Helix jobs to resubmit the failed work items..."); var retryingHelixSubmitterJobs = new HashSet(StringComparer.OrdinalIgnoreCase); var resubmittedJobs = new List(); diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index bcdcc564676..323db9e3f31 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -2640,14 +2640,14 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=warning]Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=warning]❌ Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=error]Failed work item console logs:", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=error]❌ Failed work item console logs:", StringComparison.Ordinal)); logger.Messages.Should().NotContain(message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); } @@ -2733,7 +2733,7 @@ public async Task LoopStatus_VerboseLogsFullHelixJobWorkItemList() exitCode.Should().Be(1); logger.Messages.Should().Contain(message => message.Contains("Helix job details:", StringComparison.Ordinal) - && message.Contains("└─ Helix job helix-linux [Running]", StringComparison.Ordinal) + && message.Contains("└─ 🧪 Helix job helix-linux [Running]", StringComparison.Ordinal) && message.Contains(" ├─ wi-01 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-10 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-11 (Running)", StringComparison.Ordinal) From 2f142c944e9ae3d832a3ae66fe66159d99b3e4a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:10:29 +0000 Subject: [PATCH 6/8] Reapply "Remove emoji icons from JobMonitor log statements" This reverts commit 0879cfbae8f3ff6933d219cdf61dafb5d58279e0. --- .../JobMonitor/JobMonitorRunner.cs | 17 ++++++++--------- .../JobMonitorRunnerTests.cs | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index e929c638a41..d5a8a832066 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -185,7 +185,7 @@ private async Task CancelInFlightHelixJobsAsync( try { await _helix.CancelJobAsync(job.JobName, cancellationToken); - _logger.LogInformation("🛑 Requested cancellation of Helix job {JobName}.{nl}{JobUri}", + _logger.LogInformation("Requested cancellation of Helix job {JobName}.{nl}{JobUri}", job.DisplayName, Environment.NewLine, job.DetailsUri); } catch (OperationCanceledException) @@ -304,7 +304,7 @@ private async Task RunLoopAsync( int totalWorkItems = _workItemOutcomes.Count; int failedWorkItems = _workItemOutcomes.Values.Count(passed => !passed); _logger.LogInformation( - "📊 Final summary:{nl}" + "Final summary:{nl}" + " Jobs: {TotalJobs} submitted / {ResubmittedJobs} resubmitted / {ProcessedJobs} processed{nl}" + " Work items: {TotalWorkItems} submitted / {ResubmittedWorkItems} resubmitted / {FailedWorkItems} failed", Environment.NewLine, @@ -373,7 +373,7 @@ private async Task LogHelixJobStatusAsync( JobWorkItemStatusCounts counts = GetStatusCounts(orderedJobs, workItemsByJob, completedJobNames, processedJobNames); _logger.LogInformation( - "ℹ️ Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + "Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + " {ProcessedWorkItems} processed / {CompletedWorkItems} completed / {RunningWorkItems} running / {WaitingWorkItems} waiting work items", counts.ProcessedJobs, counts.CompletedJobs, @@ -478,7 +478,7 @@ private static void AddVerboseJobLines( { string jobConnector = isLastJob ? "└─" : "├─"; string childPrefix = isLastJob ? " " : "│ "; - lines.Add($"{jobConnector} 🧪 Helix job {job.DisplayName} [{jobStatus}]"); + lines.Add($"{jobConnector} Helix job {job.DisplayName} [{jobStatus}]"); List orderedWorkItems = [ @@ -584,8 +584,7 @@ private async Task ProcessCompletedJobAsync( QueueTestResultUpload(helixJob, workItems, cancellationToken); } - _logger.LogInformation("{Icon} Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", - failedWorkItemCount == 0 ? "✅" : "❌", + _logger.LogInformation("Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", helixJob.DisplayName, failedWorkItemCount == 0 ? "succeeded" : "failed", successfulWorkItemCount, @@ -705,7 +704,7 @@ private void LogFailedWorkItemConsoleLinks(HelixJobInfo helixJob, IReadOnlyColle continue; } - LogWarning($"❌ Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); + LogWarning($"Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); } } @@ -816,7 +815,7 @@ private void LogFinalFailedWorkItemConsoleInfo() lines.Add($"{(i == failures.Count - 1 ? " " : "│ ")}└─ Console: {failure.ConsoleOutput}"); } - LogError($"❌ Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); + LogError($"Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); } private string GetTestResultsUri() @@ -827,7 +826,7 @@ private static string GetConsoleOutputText(string consoleOutputUri) private async Task ResubmitFailedJobsAsync(CancellationToken cancellationToken) { - _logger.LogInformation("🔁 Checking for failed Helix jobs to resubmit the failed work items..."); + _logger.LogInformation("Checking for failed Helix jobs to resubmit the failed work items..."); var retryingHelixSubmitterJobs = new HashSet(StringComparer.OrdinalIgnoreCase); var resubmittedJobs = new List(); diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index 323db9e3f31..bcdcc564676 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -2640,14 +2640,14 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=warning]❌ Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=warning]Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=error]❌ Failed work item console logs:", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=error]Failed work item console logs:", StringComparison.Ordinal)); logger.Messages.Should().NotContain(message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); } @@ -2733,7 +2733,7 @@ public async Task LoopStatus_VerboseLogsFullHelixJobWorkItemList() exitCode.Should().Be(1); logger.Messages.Should().Contain(message => message.Contains("Helix job details:", StringComparison.Ordinal) - && message.Contains("└─ 🧪 Helix job helix-linux [Running]", StringComparison.Ordinal) + && message.Contains("└─ Helix job helix-linux [Running]", StringComparison.Ordinal) && message.Contains(" ├─ wi-01 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-10 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-11 (Running)", StringComparison.Ordinal) From b068f5b4b54a20cfb43dc288cdedeef18bec21c5 Mon Sep 17 00:00:00 2001 From: Premek Vysoky Date: Wed, 3 Jun 2026 13:13:04 +0200 Subject: [PATCH 7/8] Revert "Reapply "Remove emoji icons from JobMonitor log statements"" This reverts commit 2f142c944e9ae3d832a3ae66fe66159d99b3e4a6. --- .../JobMonitor/JobMonitorRunner.cs | 17 +++++++++-------- .../JobMonitorRunnerTests.cs | 6 +++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index d5a8a832066..e929c638a41 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -185,7 +185,7 @@ private async Task CancelInFlightHelixJobsAsync( try { await _helix.CancelJobAsync(job.JobName, cancellationToken); - _logger.LogInformation("Requested cancellation of Helix job {JobName}.{nl}{JobUri}", + _logger.LogInformation("🛑 Requested cancellation of Helix job {JobName}.{nl}{JobUri}", job.DisplayName, Environment.NewLine, job.DetailsUri); } catch (OperationCanceledException) @@ -304,7 +304,7 @@ private async Task RunLoopAsync( int totalWorkItems = _workItemOutcomes.Count; int failedWorkItems = _workItemOutcomes.Values.Count(passed => !passed); _logger.LogInformation( - "Final summary:{nl}" + "📊 Final summary:{nl}" + " Jobs: {TotalJobs} submitted / {ResubmittedJobs} resubmitted / {ProcessedJobs} processed{nl}" + " Work items: {TotalWorkItems} submitted / {ResubmittedWorkItems} resubmitted / {FailedWorkItems} failed", Environment.NewLine, @@ -373,7 +373,7 @@ private async Task LogHelixJobStatusAsync( JobWorkItemStatusCounts counts = GetStatusCounts(orderedJobs, workItemsByJob, completedJobNames, processedJobNames); _logger.LogInformation( - "Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + "ℹ️ Status: {ProcessedJobs} processed / {CompletedJobs} completed / {RunningJobs} running / {WaitingJobs} waiting jobs{nl}" + " {ProcessedWorkItems} processed / {CompletedWorkItems} completed / {RunningWorkItems} running / {WaitingWorkItems} waiting work items", counts.ProcessedJobs, counts.CompletedJobs, @@ -478,7 +478,7 @@ private static void AddVerboseJobLines( { string jobConnector = isLastJob ? "└─" : "├─"; string childPrefix = isLastJob ? " " : "│ "; - lines.Add($"{jobConnector} Helix job {job.DisplayName} [{jobStatus}]"); + lines.Add($"{jobConnector} 🧪 Helix job {job.DisplayName} [{jobStatus}]"); List orderedWorkItems = [ @@ -584,7 +584,8 @@ private async Task ProcessCompletedJobAsync( QueueTestResultUpload(helixJob, workItems, cancellationToken); } - _logger.LogInformation("Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", + _logger.LogInformation("{Icon} Job '{JobName}' {Status} ({PassedCount} passed, {FailedCount} failed){nl}{JobUri}", + failedWorkItemCount == 0 ? "✅" : "❌", helixJob.DisplayName, failedWorkItemCount == 0 ? "succeeded" : "failed", successfulWorkItemCount, @@ -704,7 +705,7 @@ private void LogFailedWorkItemConsoleLinks(HelixJobInfo helixJob, IReadOnlyColle continue; } - LogWarning($"Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); + LogWarning($"❌ Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); } } @@ -815,7 +816,7 @@ private void LogFinalFailedWorkItemConsoleInfo() lines.Add($"{(i == failures.Count - 1 ? " " : "│ ")}└─ Console: {failure.ConsoleOutput}"); } - LogError($"Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); + LogError($"❌ Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); } private string GetTestResultsUri() @@ -826,7 +827,7 @@ private static string GetConsoleOutputText(string consoleOutputUri) private async Task ResubmitFailedJobsAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Checking for failed Helix jobs to resubmit the failed work items..."); + _logger.LogInformation("🔁 Checking for failed Helix jobs to resubmit the failed work items..."); var retryingHelixSubmitterJobs = new HashSet(StringComparer.OrdinalIgnoreCase); var resubmittedJobs = new List(); diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index bcdcc564676..323db9e3f31 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -2640,14 +2640,14 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=warning]Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=warning]❌ Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=error]Failed work item console logs:", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=error]❌ Failed work item console logs:", StringComparison.Ordinal)); logger.Messages.Should().NotContain(message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); } @@ -2733,7 +2733,7 @@ public async Task LoopStatus_VerboseLogsFullHelixJobWorkItemList() exitCode.Should().Be(1); logger.Messages.Should().Contain(message => message.Contains("Helix job details:", StringComparison.Ordinal) - && message.Contains("└─ Helix job helix-linux [Running]", StringComparison.Ordinal) + && message.Contains("└─ 🧪 Helix job helix-linux [Running]", StringComparison.Ordinal) && message.Contains(" ├─ wi-01 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-10 (Running)", StringComparison.Ordinal) && message.Contains(" ├─ wi-11 (Running)", StringComparison.Ordinal) From d3133de73ffd52f00e8c8a12e9107e282be28d6d Mon Sep 17 00:00:00 2001 From: Premek Vysoky Date: Wed, 3 Jun 2026 13:19:14 +0200 Subject: [PATCH 8/8] =?UTF-8?q?Remove=20=E2=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs | 4 ++-- .../Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs index e929c638a41..8c892826cad 100644 --- a/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs +++ b/src/Microsoft.DotNet.Helix/JobMonitor/JobMonitorRunner.cs @@ -705,7 +705,7 @@ private void LogFailedWorkItemConsoleLinks(HelixJobInfo helixJob, IReadOnlyColle continue; } - LogWarning($"❌ Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); + LogWarning($"Work item '{workItem.Name}' in job '{helixJob.DisplayName}' failed ({FormatWorkItemState(workItem)}).{Environment.NewLine}Console: {GetConsoleOutputText(workItem.ConsoleOutputUri)}"); } } @@ -816,7 +816,7 @@ private void LogFinalFailedWorkItemConsoleInfo() lines.Add($"{(i == failures.Count - 1 ? " " : "│ ")}└─ Console: {failure.ConsoleOutput}"); } - LogError($"❌ Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); + LogError($"Failed work item console logs:{Environment.NewLine}Test results: {GetTestResultsUri()}{Environment.NewLine}{string.Join(Environment.NewLine, lines)}"); } private string GetTestResultsUri() diff --git a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs index 323db9e3f31..739d13c7b59 100644 --- a/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs +++ b/src/Microsoft.DotNet.Helix/Sdk.Tests/Microsoft.DotNet.Helix.Sdk.Tests/JobMonitorRunnerTests.cs @@ -2640,14 +2640,14 @@ public async Task CompletedHelixJob_LogsFailedWorkItemConsoleLinks() logger.Messages.Should().Contain(message => message.Contains($"Work item 'wi-fail' in job 'helix-linux' failed (Finished, exit code 1).{Environment.NewLine}Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=warning]❌ Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=warning]Work item 'wi-fail' in job 'helix-linux' failed", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => message.Contains("Failed work item console logs:", StringComparison.Ordinal) && message.Contains("Test results: https://dev.azure.com/dnceng/public/_build/results?buildId=123&view=ms.vss-test-web.build-test-results-tab", StringComparison.Ordinal) && message.Contains("└─ wi-fail (Job: helix-linux) (Finished, exit code 1)", StringComparison.Ordinal) && message.Contains("└─ Console: https://helix.example/wi-fail/console", StringComparison.Ordinal)); logger.Messages.Should().Contain(message => - message.Contains("##vso[task.logissue type=error]❌ Failed work item console logs:", StringComparison.Ordinal)); + message.Contains("##vso[task.logissue type=error]Failed work item console logs:", StringComparison.Ordinal)); logger.Messages.Should().NotContain(message => message.Contains("Helix job: helix-linux", StringComparison.Ordinal)); }