Skip to content

Commit 01a92a4

Browse files
committed
fix(cli): route --verbose tracebacks to stderr too
The generic --verbose handlers in scan() and baseline() still called console.print_exception(), so a fatal traceback landed on stdout while stderr stayed empty — the exact split the rest of this PR fixes for the one-line error messages. A caller redirecting stdout to a report file got the traceback inside the file and nothing in its error log. Both branches now print through err_console, and the regression asserts the separation on both commands: RuntimeError appears in stderr and not in stdout, exit code 2. Tests: tests/unit 735 passed, 12 skipped. Signed-off-by: Mark2Mac <Mark2Mac@users.noreply.github.com>
1 parent 3bfc5f4 commit 01a92a4

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

src/skillspector/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ def scan(
386386
raise typer.Exit(code=2) from e
387387
except Exception as e:
388388
if verbose:
389-
console.print_exception()
389+
err_console.print_exception()
390390
else:
391391
err_console.print(f"[red]Error:[/red] {e}")
392392
raise typer.Exit(code=2) from e
@@ -639,7 +639,7 @@ def baseline(
639639
raise typer.Exit(code=2) from e
640640
except Exception as e:
641641
if verbose:
642-
console.print_exception()
642+
err_console.print_exception()
643643
else:
644644
err_console.print(f"[red]Error:[/red] {e}")
645645
raise typer.Exit(code=2) from e

tests/unit/test_cli.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,3 +756,30 @@ def fake_invoke(state: dict[str, Any], config: Any = None) -> dict[str, Any]:
756756
assert payload["issues"] == [{"id": "X-1", "severity": "low"}]
757757
assert payload["suppressed_count"] == 0
758758
assert payload["suppressed"] == []
759+
760+
761+
def test_scan_verbose_traceback_goes_to_stderr(tmp_path: Path) -> None:
762+
"""A fatal --verbose traceback belongs on stderr, so stdout stays parseable."""
763+
(tmp_path / "SKILL.md").write_text("# Boom", encoding="utf-8")
764+
765+
with patch("skillspector.cli.graph.invoke", side_effect=RuntimeError("scan crashed")):
766+
result = runner.invoke(app, ["scan", str(tmp_path), "--no-llm", "--verbose"])
767+
768+
assert result.exit_code == 2
769+
assert "RuntimeError" in result.stderr
770+
assert "RuntimeError" not in result.stdout
771+
772+
773+
def test_baseline_verbose_traceback_goes_to_stderr(tmp_path: Path) -> None:
774+
"""Same separation for `baseline`, which shares the generic --verbose handler."""
775+
(tmp_path / "SKILL.md").write_text("# Boom", encoding="utf-8")
776+
777+
with patch("skillspector.cli.graph.invoke", side_effect=RuntimeError("baseline crashed")):
778+
result = runner.invoke(
779+
app,
780+
["baseline", str(tmp_path), "--no-llm", "--verbose", "-o", str(tmp_path / "b.yaml")],
781+
)
782+
783+
assert result.exit_code == 2
784+
assert "RuntimeError" in result.stderr
785+
assert "RuntimeError" not in result.stdout

0 commit comments

Comments
 (0)