Skip to content

A console run (incl. the tray→terminal fallback) aborts with exit 134 when the tool's Status getter throws #79

Open

Description

@matt-edmondson

What's wrong

ConsoleRunner.Run prints its startup line through DescribeRun (TrayApp/ConsoleRunner.cs:91-100), which calls TrayAppDefinition.Describe() (TrayApp/TrayAppDefinition.cs:73):

public string Describe() => Status?.Invoke() ?? $"{DisplayName} is running";

Nothing guards the getter here. #62/#64 made --status survive a throwing getter by reading it through StatusReport.Read, and #25 covers the tray menu. The console path still calls it bare. The exception leaves Run after the finally runs OnStop, then leaves TrayAppBuilder.RunAsync, which has no handler, so the process dies with an unhandled exception. That breaks the 0/1/2 exit-code contract. The same path is reached from the fallback in RecoverFromTrayFailure, when the tray can't start and the run "stays in the terminal instead".

Reproduction

.Status(() => throw new InvalidOperationException("systemd-inhibit was not found on PATH")) with OnStart/OnStop, run with --no-tray --for 1s:

>>> OnStart
>>> OnStop
Unhandled exception. System.InvalidOperationException: systemd-inhibit was not found on PATH
   at ktsu.TrayApp.TrayAppDefinition.Describe() in TrayAppDefinition.cs:line 73
   at ktsu.TrayApp.ConsoleRunner.DescribeRun(...) in ConsoleRunner.cs:line 97
exit=134

Running with --tray and no DISPLAY gives the same result once it has printed "Staying in the terminal instead."

#62 gave the reason for its own fix: a getter is likely to throw for the same reason the tool can't work. A missing helper is exactly the case where the console fallback gets used, and there it crashes.

Suggested fix / acceptance criteria

  • Read the status in DescribeRun through StatusReport.Read(app.Status), or have Describe() catch non-fatal exceptions (FatalError.IsNotFatal). It then prints e.g. repro: error: <message>. Press Ctrl+C to stop.
  • A throwing getter never ends the process with an unhandled exception. The run either continues or returns exit code 1 with a message.
  • ConsoleRunnerTests.Run_WhenTheRunItselfThrows_StillStops currently expects the exception to propagate from Run. Adjust it, or add a test for a throwing Status specifically.

Activity

  1. matt-edmondson commented on Nov 5, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Category: Bug
    Priority: Medium. A throwing Status getter crashes console runs with exit 134 and breaks the 0/1/2 exit-code contract. As #62 argued, the getter is most likely to throw in exactly the environment where the tray→terminal fallback kicks in. The trigger is specific, and the fix is one line, routing through the existing StatusReport.Read.

    Suggested area / assignment: ConsoleRunner.DescribeRun (TrayApp/ConsoleRunner.cs:91-100) or TrayAppDefinition.Describe() (TrayApp/TrayAppDefinition.cs:73), plus ConsoleRunnerTests.

    Duplicates / in progress: Not a duplicate, but part of the same family as #24 (a throwing OnStop aborts a console run with exit 134) and #25 (throwing getters in the tray menu, now covered). #62/#64 fixed the same problem for --status. Fixing #24 and this one together would leave the console runner with one top-level non-fatal exception policy. No open PR covers it.

    Next step: Read the status through StatusReport.Read(app.Status) in DescribeRun. Add a test for a throwing Status, and adjust Run_WhenTheRunItselfThrows_StillStops if its expectation changes.


    Generated by Claude Code

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions