TUI startup errors invisible — alt-screen tears down before user can read them #6

Closed
opened 2026-05-21 22:02:46 -07:00 by vh · 1 comment
Owner

Problem

When ratatoskr.tui's on_mount encounters an error during session setup
(AgentNotFound, SessionApiFailed, network error during
create_session), it writes a labeled [error_kind] ... line to the
RichLog widget and calls self.exit(<code>). The Textual app exits, the
alt-screen tears down, and the user is left looking at a blank terminal —
the labeled line was written to the RichLog widget which only exists inside
the alt-screen. Exit code is set correctly, but there's no visible
diagnostic.

Surfaced during manual smoke 2026-05-21: ratatoskr --new --agent lofn
(which hits the issue #5 bug, 422 from server) blanks the screen for ~200ms
then quits with exit code 20. No visible diagnostic; the operator's only
recourse is to re-run the same flow under --send (which writes errors to
real stderr that survives) to see what happened.

The --send mode handles this class of error correctly: stderr labels go
to real stderr, which is the operator's terminal directly, not the
alt-screen. The TUI's flow violates the "errors should be visible to the
user" expectation that --send upholds.

Solution

Recommendation: Option A — move session-create out of on_mount and
into run_tui (sync, BEFORE App.run() opens the alt-screen).
Surface
errors to stderr at the run_tui layer; only enter the alt-screen after
session-create succeeds.

This aligns the TUI's startup-error UX with the CLI's: failures before the
event loop opens print to stderr; failures during streaming render in
the alt-screen (where mid-session errors per INV-008 already go).

Implementation shape:

def run_tui(args: ParsedArgs) -> int:
    # ... existing PRE-001/002 asserts ...
    # NEW: session-create BEFORE App.run() — errors print to real stderr
    try:
        session_id, agent_id = asyncio.run(_resolve_session(args))
    except AgentNotFound as exc:
        sys.stderr.write(f"[agent_not_found] agent_id={exc.agent_id}\n")
        return 12
    except SessionApiFailed as exc:
        sys.stderr.write(f"[session_api_failed] status={exc.status} body={exc.body!r}\n")
        return 20
    except (httpx.ConnectError, httpx.ReadTimeout, httpx.TransportError) as exc:
        sys.stderr.write(f"[network_error] {type(exc).__name__}: {exc}\n")
        return 21
    # session resolved; enter the alt-screen with identity already known
    app = RatatoskrApp(args, session_id=session_id, agent_id=agent_id)
    return app.run() or 0

RatatoskrApp.__init__ gains session_id / agent_id parameters (pre-
resolved). on_mount no longer creates the session; it just opens the
AsyncClient and populates the identity widgets.

Alternative options considered + rejected:

  • B. Dump RichLog content to stderr after app.run() returns non-zero.
    Requires the App to expose its log content after exit; relies on widget
    content surviving teardown. Adds a post-exit dump step that feels like
    workaround.
  • C. Persistent error log file at ~/.cache/ratatoskr/last-error.log.
    Rejected per design-brief §8d ("no cross-process resume, no config dir").

Out of scope (this issue)

  • General TUI logging infrastructure (e.g., a separate DiagnosticsLog
    surface). Each side pane is its own issue.
  • Mid-session errors during streaming — those already render to
    RichLog and return to idle per INV-008's "errors don't exit the app"
    contract; the user reads them in the TUI. Only startup-phase errors
    need pre-alt-screen visibility.
  • Persistent error log file — rejected above.
  • 422 user-friendly hints — see issue #5's optional follow-up.

Benefits

  • Operator can see what went wrong without re-invoking under --send to
    diagnose. Fixes the "screen blanks then quits" UX surfaced 2026-05-21.
  • Aligns TUI startup-error UX with CLI's stderr-labels UX.
  • Catches issue #5's class of bug (and any future agent-required-field
    bugs) before the alt-screen masks them.
  • Clean lifecycle separation: failures before App.run() → real stderr;
    failures inside App.run() (mid-stream) → RichLog per INV-008.

Acceptance

  • Contract amendment to issue #4: run_tui STEPS expanded with the pre-
    App.run() session-resolve step; RatatoskrApp.__init__ signature widened;
    on_mount STEPS narrowed to client-open + identity-widget-populate.
  • Tests updated: TestAppMount tests no longer drive session-create through
    on_mount (it's gone); new TestRunTui tests cover the pre-App.run()
    error paths via stderr capture.
  • All tests pass; ruff clean; drift check clean.
  • Manual smoke: ratatoskr --new --agent <nonexistent> produces a visible
    [agent_not_found] line on stderr; no screen-blanking artifact.

Dependencies

  • Issue #4 (ratatoskr.tui shell) — landed on main; this issue amends its
    contract.
  • Composes naturally with issue #5 — once #5 lands, the typical
    wrong-input flow for per-user agents becomes 422-from-server which #6
    must surface. But #6 is independent in scope; either can land first.
## Problem When `ratatoskr.tui`'s `on_mount` encounters an error during session setup (`AgentNotFound`, `SessionApiFailed`, network error during `create_session`), it writes a labeled `[error_kind] ...` line to the RichLog widget and calls `self.exit(<code>)`. The Textual app exits, the alt-screen tears down, and the user is left looking at a blank terminal — the labeled line was written to the RichLog widget which only exists inside the alt-screen. Exit code is set correctly, but there's no visible diagnostic. Surfaced during manual smoke 2026-05-21: `ratatoskr --new --agent lofn` (which hits the issue #5 bug, 422 from server) blanks the screen for ~200ms then quits with exit code 20. No visible diagnostic; the operator's only recourse is to re-run the same flow under `--send` (which writes errors to real stderr that survives) to see what happened. The `--send` mode handles this class of error correctly: stderr labels go to real stderr, which is the operator's terminal directly, not the alt-screen. The TUI's flow violates the "errors should be visible to the user" expectation that `--send` upholds. ## Solution **Recommendation: Option A — move session-create out of `on_mount` and into `run_tui` (sync, BEFORE App.run() opens the alt-screen).** Surface errors to stderr at the `run_tui` layer; only enter the alt-screen after session-create succeeds. This aligns the TUI's startup-error UX with the CLI's: failures before the event loop opens print to stderr; failures during streaming render in the alt-screen (where mid-session errors per INV-008 already go). **Implementation shape:** ``` def run_tui(args: ParsedArgs) -> int: # ... existing PRE-001/002 asserts ... # NEW: session-create BEFORE App.run() — errors print to real stderr try: session_id, agent_id = asyncio.run(_resolve_session(args)) except AgentNotFound as exc: sys.stderr.write(f"[agent_not_found] agent_id={exc.agent_id}\n") return 12 except SessionApiFailed as exc: sys.stderr.write(f"[session_api_failed] status={exc.status} body={exc.body!r}\n") return 20 except (httpx.ConnectError, httpx.ReadTimeout, httpx.TransportError) as exc: sys.stderr.write(f"[network_error] {type(exc).__name__}: {exc}\n") return 21 # session resolved; enter the alt-screen with identity already known app = RatatoskrApp(args, session_id=session_id, agent_id=agent_id) return app.run() or 0 ``` `RatatoskrApp.__init__` gains `session_id` / `agent_id` parameters (pre- resolved). `on_mount` no longer creates the session; it just opens the AsyncClient and populates the identity widgets. **Alternative options considered + rejected:** - **B. Dump RichLog content to stderr after `app.run()` returns non-zero.** Requires the App to expose its log content after exit; relies on widget content surviving teardown. Adds a post-exit dump step that feels like workaround. - **C. Persistent error log file at `~/.cache/ratatoskr/last-error.log`.** Rejected per design-brief §8d ("no cross-process resume, no config dir"). ## Out of scope (this issue) - **General TUI logging infrastructure** (e.g., a separate DiagnosticsLog surface). Each side pane is its own issue. - **Mid-session errors during streaming** — those already render to RichLog and return to idle per INV-008's "errors don't exit the app" contract; the user reads them in the TUI. Only startup-phase errors need pre-alt-screen visibility. - **Persistent error log file** — rejected above. - **422 user-friendly hints** — see issue #5's optional follow-up. ## Benefits - Operator can see what went wrong without re-invoking under `--send` to diagnose. Fixes the "screen blanks then quits" UX surfaced 2026-05-21. - Aligns TUI startup-error UX with CLI's stderr-labels UX. - Catches issue #5's class of bug (and any future agent-required-field bugs) before the alt-screen masks them. - Clean lifecycle separation: failures before App.run() → real stderr; failures inside App.run() (mid-stream) → RichLog per INV-008. ## Acceptance - Contract amendment to issue #4: `run_tui` STEPS expanded with the pre- App.run() session-resolve step; `RatatoskrApp.__init__` signature widened; `on_mount` STEPS narrowed to client-open + identity-widget-populate. - Tests updated: `TestAppMount` tests no longer drive session-create through `on_mount` (it's gone); new `TestRunTui` tests cover the pre-App.run() error paths via stderr capture. - All tests pass; ruff clean; drift check clean. - Manual smoke: `ratatoskr --new --agent <nonexistent>` produces a visible `[agent_not_found]` line on stderr; no screen-blanking artifact. ## Dependencies - Issue #4 (`ratatoskr.tui` shell) — landed on main; this issue amends its contract. - Composes naturally with issue #5 — once #5 lands, the typical wrong-input flow for per-user agents becomes 422-from-server which #6 must surface. But #6 is independent in scope; either can land first.
vh added the bugtuienhancementtask labels 2026-05-21 22:02:46 -07:00
Author
Owner

Closed — shipped. TUI startup error visibility implemented via TDD, two Volva rounds (commit 804c2df). _resolve_then_run routes pre-flight errors to stderr BEFORE alt-screen opens.

Closed — shipped. TUI startup error visibility implemented via TDD, two Volva rounds (commit 804c2df). _resolve_then_run routes pre-flight errors to stderr BEFORE alt-screen opens.
vh closed this issue 2026-05-29 23:27:32 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vh/ratatoskr#6