Skip to content

Commit 79197b2

Browse files
fix: skip metadata-only JSONL entry types in role fallback path
1 parent 7c6fa96 commit 79197b2

4 files changed

Lines changed: 19 additions & 20 deletions

File tree

tests/test_jsonl_parser.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -737,22 +737,6 @@ def test_empty_file_returns_skeleton(self):
737737
finally:
738738
os.unlink(path)
739739

740-
def test_unknown_entry_type_maps_to_system(self, caplog):
741-
path = _write_jsonl(
742-
[
743-
{"type": "custom", "timestamp": "2026-01-01T00:00:00Z"},
744-
]
745-
)
746-
try:
747-
with caplog.at_level("WARNING"):
748-
s = parse_session(path)
749-
assert len(s["messages"]) == 1
750-
assert s["messages"][0]["role"] == "system"
751-
assert s["metadata"]["entry_counts"].get("custom") == 1
752-
assert "Unknown message role" in caplog.text
753-
finally:
754-
os.unlink(path)
755-
756740
def test_is_sidechain_increments_counter(self):
757741
path = _write_jsonl(
758742
[
@@ -783,6 +767,7 @@ def test_file_history_snapshot_timestamp(self):
783767
s = parse_session(path)
784768
assert s["metadata"]["first_timestamp"] == "2026-01-02T12:00:00Z"
785769
assert s["metadata"]["last_timestamp"] == "2026-01-02T12:00:00Z"
770+
assert len(s["messages"]) == 0
786771
finally:
787772
os.unlink(path)
788773

tests/test_jsonl_validation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ def test_valid_payload_returns_session_dict(self):
7070
assert result["session_id"] == "abc123"
7171
assert result["messages"][0]["role"] == "user"
7272

73+
def test_invalid_role_in_message(self):
74+
with pytest.raises(SessionValidationError) as exc_info:
75+
validate_session_dict(_valid_payload(messages=[{"role": "custom", "text": "x"}]))
76+
assert exc_info.value.path == "messages[0].role"
77+
assert "custom" in exc_info.value.detail
78+
7379

7480
class TestParseSessionValidationRegression:
7581
def test_session_minimal_fixture_unchanged(self):

utils/jsonl_parser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
]
4343

4444
_HANDLED_ENTRY_TYPES = frozenset({"user", "assistant", "system", "progress"})
45+
_SKIP_ENTRY_TYPES = frozenset({"file-history-snapshot"})
4546
_VALID_ROLES = frozenset(get_args(RoleLiteral))
4647

4748

@@ -171,7 +172,7 @@ def parse_session(filepath: str) -> SessionDict:
171172
_process_progress(entry, messages)
172173
elif entry_type:
173174
type_str = entry_type if isinstance(entry_type, str) else str(entry_type)
174-
if type_str not in _HANDLED_ENTRY_TYPES:
175+
if type_str not in _HANDLED_ENTRY_TYPES and type_str not in _SKIP_ENTRY_TYPES:
175176
messages.append(_fallback_message(entry, _coerce_role(type_str)))
176177

177178
metadata["models_used"] = sorted(metadata["models_used"])

utils/validation.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Runtime validation for TypedDict shapes at untrusted-data boundaries."""
22

3-
from typing import Any, cast
3+
from typing import Any, cast, get_args
44

55
from models.errors import SessionValidationError
6-
from models.session import SessionDict
6+
from models.session import RoleLiteral, SessionDict
7+
8+
_VALID_ROLES = frozenset(get_args(RoleLiteral))
79

810
_ROOT_PATH = "(root)"
911

@@ -49,6 +51,11 @@ def validate_session_dict(data: dict[str, Any]) -> SessionDict:
4951
for index, message in enumerate(messages):
5052
path = f"messages[{index}]"
5153
msg_dict = _require_value(path, message, dict, "dict")
52-
_require_field(msg_dict, "role", str, "str", path=f"{path}.role")
54+
role = _require_field(msg_dict, "role", str, "str", path=f"{path}.role")
55+
if role not in _VALID_ROLES:
56+
raise SessionValidationError(
57+
f"{path}.role",
58+
f"expected one of {sorted(_VALID_ROLES)!r}, got {role!r}",
59+
)
5360

5461
return cast(SessionDict, data)

0 commit comments

Comments
 (0)