From 9a204e554e1ebecb61a07ef3e48142bed986fb77 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 11:27:33 +0000 Subject: [PATCH] [patch] Read the status line of a console run through StatusReport.Read ConsoleRunner.DescribeRun called the tool's Status getter bare, so a getter that threw (likely in exactly the environment that sends a tray run to the console fallback) escaped Run and TrayAppBuilder.RunAsync and ended the process with exit 134. It is now read the way --status reads it: the run prints ": error: ", keeps running, and stops with its normal exit code. Fixes ktsu-dev/TrayApp#79 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW31r4TeF4cogSkLxEi --- TrayApp.Test/ConsoleRunnerTests.cs | 24 ++++++++++++++++++++---- TrayApp/ConsoleRunner.cs | 7 ++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/TrayApp.Test/ConsoleRunnerTests.cs b/TrayApp.Test/ConsoleRunnerTests.cs index f0dd286..b00dcaa 100644 --- a/TrayApp.Test/ConsoleRunnerTests.cs +++ b/TrayApp.Test/ConsoleRunnerTests.cs @@ -88,7 +88,7 @@ public void Run_WhenTheToolRefusesToStart_ExitsOneWithoutStopping() } [TestMethod] - public void Run_WhenTheRunItselfThrows_StillStops() + public void Run_WhenTheStatusThrows_ReportsItAndStillRunsAndStops() { bool stopped = false; @@ -97,10 +97,26 @@ public void Run_WhenTheRunItselfThrows_StillStops() .OnStop(() => stopped = true) .Build(); - // Describing the run is the tool's code too. Whatever it does, the release on the way out is the - // library's promise, so it happens in a finally rather than after the last statement. - Assert.ThrowsExactly(() => ConsoleRunner.Run(app, ShortRun())); + // Describing the run is the tool's code too, and a getter that throws is most likely to do so in the + // environment that sends a run here, so it is the line's text, as in --status, not an unhandled + // exception that breaks the exit-code contract. + using System.IO.StringWriter output = new(); + System.IO.TextWriter original = Console.Out; + Console.SetOut(output); + int exitCode; + try + { + exitCode = ConsoleRunner.Run(app, ShortRun()); + } + finally + { + Console.SetOut(original); + } + + Assert.AreEqual(0, exitCode); Assert.IsTrue(stopped); + Assert.Contains("demo: error: the status broke, stopping after", output.ToString()); + Assert.Contains("demo: stopped.", output.ToString()); } [TestMethod] diff --git a/TrayApp/ConsoleRunner.cs b/TrayApp/ConsoleRunner.cs index 2b0da29..afcf132 100644 --- a/TrayApp/ConsoleRunner.cs +++ b/TrayApp/ConsoleRunner.cs @@ -94,9 +94,14 @@ private static string DescribeRun(TrayAppDefinition app, CommandLineOptions opti ? string.Create(CultureInfo.InvariantCulture, $", stopping after {window:g}") : string.Empty; + // The getter is the tool's code, and it is most likely to throw for the same reason the tool cannot + // work - a missing helper, say - which is also when the tray falls back to this mode. It is read the + // way --status reads it, so it becomes the line's text rather than an unhandled exception. + string state = app.Status is Func status ? StatusReport.Read(status) : app.Describe(); + return string.Create( CultureInfo.InvariantCulture, - $"{app.Name}: {app.Describe()}{until}. Press Ctrl+C to stop."); + $"{app.Name}: {state}{until}. Press Ctrl+C to stop."); } ///