Skip to content

Commit f617861

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 df22f09 commit f617861

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
@@ -351,7 +351,7 @@ def scan(
351351
raise typer.Exit(code=2) from e
352352
except Exception as e:
353353
if verbose:
354-
console.print_exception()
354+
err_console.print_exception()
355355
else:
356356
err_console.print(f"[red]Error:[/red] {e}")
357357
raise typer.Exit(code=2) from e
@@ -604,7 +604,7 @@ def baseline(
604604
raise typer.Exit(code=2) from e
605605
except Exception as e:
606606
if verbose:
607-
console.print_exception()
607+
err_console.print_exception()
608608
else:
609609
err_console.print(f"[red]Error:[/red] {e}")
610610
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
@@ -690,3 +690,30 @@ def fake_invoke(state: dict[str, Any], config: Any = None) -> dict[str, Any]:
690690
assert payload["issues"] == [{"id": "X-1", "severity": "low"}]
691691
assert payload["suppressed_count"] == 0
692692
assert payload["suppressed"] == []
693+
694+
695+
def test_scan_verbose_traceback_goes_to_stderr(tmp_path: Path) -> None:
696+
"""A fatal --verbose traceback belongs on stderr, so stdout stays parseable."""
697+
(tmp_path / "SKILL.md").write_text("# Boom", encoding="utf-8")
698+
699+
with patch("skillspector.cli.graph.invoke", side_effect=RuntimeError("scan crashed")):
700+
result = runner.invoke(app, ["scan", str(tmp_path), "--no-llm", "--verbose"])
701+
702+
assert result.exit_code == 2
703+
assert "RuntimeError" in result.stderr
704+
assert "RuntimeError" not in result.stdout
705+
706+
707+
def test_baseline_verbose_traceback_goes_to_stderr(tmp_path: Path) -> None:
708+
"""Same separation for `baseline`, which shares the generic --verbose handler."""
709+
(tmp_path / "SKILL.md").write_text("# Boom", encoding="utf-8")
710+
711+
with patch("skillspector.cli.graph.invoke", side_effect=RuntimeError("baseline crashed")):
712+
result = runner.invoke(
713+
app,
714+
["baseline", str(tmp_path), "--no-llm", "--verbose", "-o", str(tmp_path / "b.yaml")],
715+
)
716+
717+
assert result.exit_code == 2
718+
assert "RuntimeError" in result.stderr
719+
assert "RuntimeError" not in result.stdout

0 commit comments

Comments
 (0)