From 3b5cfde905af50306e74332cc4cbf41c7a7a25e7 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Fri, 17 Jul 2026 05:01:13 -0700 Subject: [PATCH] fix: report fatal errors in plain print mode Capture unrecoverable ErrorEvent messages and write them to stderr when JSON output is disabled. Add a CLI regression test for the silent-error path. --- reigner/cli/chat.py | 8 +++++--- tests/cli/test_chat.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/reigner/cli/chat.py b/reigner/cli/chat.py index a263ab8..588db09 100644 --- a/reigner/cli/chat.py +++ b/reigner/cli/chat.py @@ -236,7 +236,7 @@ async def _run_print(session: Session, query: str, *, json_output: bool) -> int: """Drive one query in headless mode. Returns the process exit code.""" final: FinalAnswerEvent | None = None saw_clarification = False - saw_error = False + error_text: str | None = None async for event in session.run_stream(query): if json_output: print(to_json(event), flush=True) @@ -245,7 +245,7 @@ async def _run_print(session: Session, query: str, *, json_output: bool) -> int: elif isinstance(event, ClarificationEvent): saw_clarification = True elif isinstance(event, ErrorEvent) and not event.recoverable: - saw_error = True + error_text = event.error if not json_output and final is not None: print(final.text) @@ -259,7 +259,9 @@ async def _run_print(session: Session, query: str, *, json_output: bool) -> int: err=True, ) return EXIT_USAGE - if saw_error: + if error_text is not None: + if not json_output: + typer.echo(f"error: {error_text}", err=True) return EXIT_RUNTIME return EXIT_RUNTIME diff --git a/tests/cli/test_chat.py b/tests/cli/test_chat.py index 8ba272e..46d3001 100644 --- a/tests/cli/test_chat.py +++ b/tests/cli/test_chat.py @@ -38,6 +38,28 @@ def test_print_plain_outputs_final_answer_only(patch_build_session) -> None: assert result.stdout.strip() == "the answer is 42" +def test_print_plain_reports_fatal_error(monkeypatch: pytest.MonkeyPatch) -> None: + from reigner.cli import chat as chat_module + from reigner.harness.events import ErrorEvent + + class ErrorSession: + async def run_stream(self, query): + yield ErrorEvent( + seq=1, + session_id="test", + turn=1, + error="adapter: openai package not installed", + recoverable=False, + ) + + monkeypatch.setattr(chat_module, "_build_session", lambda _path: ErrorSession()) + result = runner.invoke(app, ["chat", "--print", "anything"]) + + assert result.exit_code == 1 + assert result.stdout == "" + assert result.stderr == "error: adapter: openai package not installed\n" + + def test_print_json_emits_nd_json_event_stream(patch_build_session) -> None: from tests.cli.conftest import _final