From b956091bebac0f838ae8f614b6b61d47b223c873 Mon Sep 17 00:00:00 2001 From: Hemanth Chittanuru Date: Thu, 13 Aug 2026 22:31:26 -0400 Subject: [PATCH] fix(cli): a bad input path is a message, not a traceback Found while installing from PyPI into a clean environment and using it the way a stranger would. Two ordinary mistakes both produced a full stack trace: a typo'd filename raised FileNotFoundError out of turns_from_jsonl, and a file that is not newline-delimited JSON raised JSONDecodeError from inside the parser. A traceback answers neither question and tells the user the tool crashed, which is untrue. Both are now SystemExit with a message that says what was wrong and, for the JSONL case, what the file was supposed to contain. Pinned. The argparse error for a missing source and the Langfuse credentials check were already clean; these were the two paths that were not. Co-Authored-By: Claude Opus 5 --- postflight/__main__.py | 13 ++++++++++++- tests/test_cli.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/postflight/__main__.py b/postflight/__main__.py index aff80ae..1241077 100644 --- a/postflight/__main__.py +++ b/postflight/__main__.py @@ -42,7 +42,18 @@ def _load(args: argparse.Namespace) -> list[Turn]: if args.otel: from .adapters.otel import turns_from_jsonl - return turns_from_jsonl(args.otel) + # A typo'd path and a file that is not JSONL are both ordinary mistakes, and a + # traceback answers neither of them. SystemExit prints the message and sets a + # non-zero status without pretending the tool crashed. + try: + return turns_from_jsonl(args.otel) + except OSError as exc: + raise SystemExit(f"cannot read {args.otel}: {exc.strerror}") from exc + except json.JSONDecodeError as exc: + raise SystemExit( + f"{args.otel} is not newline-delimited JSON: {exc} " + "(expected one exported span object per line)" + ) from exc from .adapters.langfuse import LangfuseAdapter, LangfuseClient diff --git a/tests/test_cli.py b/tests/test_cli.py index 68f4e8a..99f5190 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -59,3 +59,17 @@ def test_langfuse_without_credentials_fails_loudly(monkeypatch): def test_a_source_is_required(): with pytest.raises(SystemExit): main([]) + + +def test_a_missing_file_is_a_message_not_a_traceback(): + """A typo'd path is an ordinary mistake. Answering it with a stack trace tells the + user the tool crashed, which is both unhelpful and untrue.""" + with pytest.raises(SystemExit, match="cannot read"): + main(["--otel", "/nonexistent/spans.jsonl"]) + + +def test_a_file_that_is_not_jsonl_says_so(tmp_path): + bad = tmp_path / "notes.txt" + bad.write_text("these are my notes, not spans\n") + with pytest.raises(SystemExit, match="not newline-delimited JSON"): + main(["--otel", str(bad)])