From 3a0e4788db8ac6fd3ef67349b01d0947fbfb3201 Mon Sep 17 00:00:00 2001 From: Sasa Junuzovic <44276455+microsasa@users.noreply.github.com> Date: Sat, 18 Apr 2026 22:20:40 -0700 Subject: [PATCH] fix: guard active_user_messages/active_output_tokens in _build_completed_summary (#992) Apply the same 'if resume.session_resumed else 0' guard to active_user_messages and active_output_tokens that already protects active_model_calls, making the contract explicit rather than relying on an implicit invariant in _first_pass. Add test that constructs a _ResumeInfo with session_resumed=False but non-zero post-shutdown counters and verifies the built summary zeros them out. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/copilot_usage/parser.py | 8 ++++++-- tests/copilot_usage/test_parser.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/copilot_usage/parser.py b/src/copilot_usage/parser.py index 43a49b82..f0ed8f87 100644 --- a/src/copilot_usage/parser.py +++ b/src/copilot_usage/parser.py @@ -957,8 +957,12 @@ def _build_completed_summary( active_model_calls=( resume.post_shutdown_turn_starts if resume.session_resumed else 0 ), - active_user_messages=resume.post_shutdown_user_messages, - active_output_tokens=resume.post_shutdown_output_tokens, + active_user_messages=( + resume.post_shutdown_user_messages if resume.session_resumed else 0 + ), + active_output_tokens=( + resume.post_shutdown_output_tokens if resume.session_resumed else 0 + ), shutdown_cycles=shutdown_cycles, events_path=events_path, ) diff --git a/tests/copilot_usage/test_parser.py b/tests/copilot_usage/test_parser.py index c1516253..95a3a91f 100644 --- a/tests/copilot_usage/test_parser.py +++ b/tests/copilot_usage/test_parser.py @@ -6470,6 +6470,34 @@ def test_stale_resume_time_cleared_across_shutdowns(self, tmp_path: Path) -> Non assert fp.last_resume_time is None assert fp.post_shutdown_user_messages == 1 + def test_completed_session_zeroes_active_counters_despite_post_shutdown_values( + self, tmp_path: Path + ) -> None: + """_build_completed_summary must zero active counters when session_resumed=False.""" + from copilot_usage.models import has_active_period_stats + + p = tmp_path / "s" / "events.jsonl" + _write_events(p, _START_EVENT, _USER_MSG, _SHUTDOWN_EVENT) + events = parse_events(p) + fp = _first_pass(events) + + # Manually construct a _ResumeInfo with session_resumed=False but + # non-zero post-shutdown counters — the invariant violation scenario. + resume = _ResumeInfo( + session_resumed=False, + post_shutdown_output_tokens=500, + post_shutdown_turn_starts=3, + post_shutdown_user_messages=2, + last_resume_time=None, + ) + summary = _build_completed_summary(fp, name=None, resume=resume, events=events) + + assert summary.is_active is False + assert summary.active_model_calls == 0 # already guarded + assert summary.active_user_messages == 0 # must be 0 regardless + assert summary.active_output_tokens == 0 # must be 0 regardless + assert not has_active_period_stats(summary) + # --------------------------------------------------------------------------- # ---------------------------------------------------------------------------