Skip to content

Commit 3f6db99

Browse files
fix(jsonl): coerce null message before parsing JSONL entries
1 parent e6df529 commit 3f6db99

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

tests/test_jsonl_parser.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,36 @@ def test_missing_usage_dict_no_crash(self):
729729
finally:
730730
os.unlink(path)
731731

732+
def test_null_message_assistant_no_crash(self):
733+
path = _write_jsonl([
734+
{
735+
"type": "assistant",
736+
"timestamp": "2026-01-01T00:00:00Z",
737+
"message": None,
738+
},
739+
])
740+
try:
741+
s = parse_session(path)
742+
assert s["metadata"]["total_input_tokens"] == 0
743+
assert len(s["messages"]) == 1
744+
assert s["messages"][0]["role"] == "assistant"
745+
finally:
746+
os.unlink(path)
747+
748+
def test_non_dict_usage_assistant_no_crash(self):
749+
path = _write_jsonl([
750+
{
751+
"type": "assistant",
752+
"timestamp": "2026-01-01T00:00:00Z",
753+
"message": {"model": "m", "content": [], "usage": "invalid"},
754+
},
755+
])
756+
try:
757+
s = parse_session(path)
758+
assert s["metadata"]["total_input_tokens"] == 0
759+
finally:
760+
os.unlink(path)
761+
732762

733763
# ---------------------------------------------------------------------------
734764
# quick_session_info

utils/jsonl_parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def _process_user(entry: dict, messages: list, metadata: dict):
137137
if metadata["permission_mode"] is None:
138138
metadata["permission_mode"] = entry.get("permissionMode")
139139

140-
msg = entry.get("message", {})
140+
msg = entry.get("message") or {}
141141
content = msg.get("content", [])
142142
text = _extract_text(content)
143143
images = _extract_images(content)
@@ -170,7 +170,7 @@ def _process_user(entry: dict, messages: list, metadata: dict):
170170
def _process_assistant(entry: dict, messages: list, metadata: dict):
171171
"""Handle assistant responses -- splits content into text, thinking blocks,
172172
and tool_use calls, and accumulates token/model/tool stats."""
173-
msg = entry.get("message", {})
173+
msg = entry.get("message") or {}
174174
model = msg.get("model", "")
175175
if model and model != "<synthetic>":
176176
metadata["models_used"].add(model)
@@ -503,7 +503,7 @@ def quick_session_info(filepath: str) -> dict:
503503
last_ts = ts # keep updating in case file is small
504504

505505
if title is None and entry.get("type") == "user":
506-
msg = entry.get("message", {})
506+
msg = entry.get("message") or {}
507507
text = _extract_text(msg.get("content", []))
508508
if text:
509509
clean = _strip_system_tags(text).strip()

0 commit comments

Comments
 (0)