From 3398e0c10bc3f3d6c75da5e72198b3639014faeb Mon Sep 17 00:00:00 2001 From: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:26:11 -0700 Subject: [PATCH] fix(cli): escape exception text in agents-list error handler (#465) `jarvis agents list` crashed with a secondary Rich MarkupError when the underlying exception message contained markup metacharacters like `[...]`: the error handler did `console.print(f"[red]Error: {exc}[/red]")`, and Rich re-parsed the interpolated message as markup (#297). Escape the dynamic message with rich.markup.escape and keep only the static "Error:" label styled, so the original error surfaces cleanly instead of a traceback. Adds a regression test that reproduces the MarkupError via an exception message with brackets. Co-authored-by: Claude Opus 4.8 (1M context) --- src/openjarvis/cli/agent_cmd.py | 8 +++++++- tests/cli/test_agent_cmd.py | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/openjarvis/cli/agent_cmd.py b/src/openjarvis/cli/agent_cmd.py index 83ae51b6..15de4510 100644 --- a/src/openjarvis/cli/agent_cmd.py +++ b/src/openjarvis/cli/agent_cmd.py @@ -88,7 +88,13 @@ def list_agents() -> None: ) console.print(table) except Exception as exc: - console.print(f"[red]Error: {exc}[/red]") + # Escape the dynamic exception text: Rich re-parses console.print + # arguments as markup, so an exception message containing "[...]" + # raises a secondary MarkupError (see #297). Keep the static + # "Error:" label styled; render the message literally. + from rich.markup import escape + + console.print(f"[red]Error:[/red] {escape(str(exc))}") @agent.command("create") diff --git a/tests/cli/test_agent_cmd.py b/tests/cli/test_agent_cmd.py index 8a71c579..7dc2f9aa 100644 --- a/tests/cli/test_agent_cmd.py +++ b/tests/cli/test_agent_cmd.py @@ -2,6 +2,8 @@ from __future__ import annotations +from unittest.mock import patch + from click.testing import CliRunner from openjarvis.cli import cli @@ -12,6 +14,25 @@ class TestAgentCmd: result = CliRunner().invoke(cli, ["agents", "list", "--help"]) assert result.exit_code == 0 + def test_agent_list_error_with_markup_chars_does_not_crash(self) -> None: + """Regression for #297. + + When ``_get_manager()`` (or anything in the body) raises an + exception whose message contains Rich markup metacharacters like + ``[...]``, the error handler must not re-parse that text as markup + and blow up with a secondary MarkupError traceback. The command + should print a clean one-line error instead. + """ + # A message with bracketed text is what triggers the MarkupError + # when fed to console.print() unescaped. + boom = RuntimeError("DB locked at [/var/run/x] not a [valid] tag") + with patch("openjarvis.cli.agent_cmd._get_manager", side_effect=boom): + result = CliRunner().invoke(cli, ["agents", "list"]) + # No traceback / unhandled exception leaked through. + assert result.exception is None, result.output + # The raw message (brackets intact) is surfaced to the user. + assert "DB locked at [/var/run/x] not a [valid] tag" in result.output + def test_agent_info_help(self) -> None: result = CliRunner().invoke(cli, ["agents", "info", "--help"]) assert result.exit_code == 0