From 3fb300b592a4c4cd8571bec1eae90ded81d97046 Mon Sep 17 00:00:00 2001 From: Dominique Broeglin Date: Sun, 5 Jul 2026 16:16:57 +0200 Subject: [PATCH] feat: warn on skipped malformed JSON lines in session logs --- src/copilot_experiments/sessionlog.py | 13 +++++++++++++ tests/test_sessionlog.py | 26 ++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/copilot_experiments/sessionlog.py b/src/copilot_experiments/sessionlog.py index d31ecc2..60b3711 100644 --- a/src/copilot_experiments/sessionlog.py +++ b/src/copilot_experiments/sessionlog.py @@ -8,9 +8,13 @@ from pathlib import Path from typing import Any +from rich.console import Console + from . import pricing from .models import Metrics, ModelMetric, TokenEconomics +err = Console(stderr=True) + def session_state_root(base: Path | None = None) -> Path: """Directory where the Copilot CLI stores per-session state.""" @@ -27,6 +31,7 @@ def load_events(path: Path) -> list[dict[str, Any]]: if not path.exists(): return [] events: list[dict[str, Any]] = [] + skipped_lines = 0 for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line: @@ -34,7 +39,15 @@ def load_events(path: Path) -> list[dict[str, Any]]: try: events.append(json.loads(line)) except json.JSONDecodeError: + skipped_lines += 1 continue + + if skipped_lines > 0: + err.print( + f"[yellow]Warning: Skipped {skipped_lines} malformed JSON line(s) " + f"in {path.name}[/yellow]" + ) + return events diff --git a/tests/test_sessionlog.py b/tests/test_sessionlog.py index ea49b1b..a44d7ee 100644 --- a/tests/test_sessionlog.py +++ b/tests/test_sessionlog.py @@ -2,7 +2,9 @@ from __future__ import annotations -from copilot_experiments.sessionlog import parse_metrics +from pathlib import Path + +from copilot_experiments.sessionlog import load_events, parse_metrics def _events(): @@ -61,7 +63,7 @@ def test_parse_metrics_empty(): # --------------------------------------------------------------------------- # -def test_load_events_skips_blank_and_invalid_lines(tmp_path): +def test_load_events_skips_blank_and_invalid_lines(tmp_path, capsys): from copilot_experiments.sessionlog import load_events path = tmp_path / "events.jsonl" @@ -72,6 +74,9 @@ def test_load_events_skips_blank_and_invalid_lines(tmp_path): events = load_events(path) assert [e["type"] for e in events] == ["session.start", "user.message"] + out, err = capsys.readouterr() + assert "Skipped 1 malformed JSON line(s)" in err + def test_load_events_missing_file_returns_empty(tmp_path): from copilot_experiments.sessionlog import load_events @@ -121,3 +126,20 @@ def test_compaction_start_tracks_peak_without_shutdown(): econ = extract_economics(events) assert econ.total_tokens is None # no shutdown -> no authoritative totals assert econ.peak_context_tokens == 9500 # 1000 + 8000 + 500 + + +def test_load_events_with_malformed_lines(tmp_path: Path, capsys): + events_file = tmp_path / "events.jsonl" + events_file.write_text( + '{"type": "session.start"}\n' + '{"type": "malformed"\n' # bad json + '{"type": "session.end"}\n', + encoding="utf-8", + ) + events = load_events(events_file) + assert len(events) == 2 + assert events[0]["type"] == "session.start" + assert events[1]["type"] == "session.end" + + out, err = capsys.readouterr() + assert "Skipped 1 malformed JSON line(s)" in err