Skip to content

Commit d899603

Browse files
fix: skip metadata-only JSONL entry types in role fallback path
1 parent c698c4a commit d899603

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
@@ -686,22 +686,6 @@ def test_empty_file_returns_skeleton(self):
686686
finally:
687687
os.unlink(path)
688688

689-
def test_unknown_entry_type_maps_to_system(self, caplog):
690-
path = _write_jsonl(
691-
[
692-
{"type": "custom", "timestamp": "2026-01-01T00:00:00Z"},
693-
]
694-
)
695-
try:
696-
with caplog.at_level("WARNING"):
697-
s = parse_session(path)
698-
assert len(s["messages"]) == 1
699-
assert s["messages"][0]["role"] == "system"
700-
assert s["metadata"]["entry_counts"].get("custom") == 1
701-
assert "Unknown message role" in caplog.text
702-
finally:
703-
os.unlink(path)
704-
705689
def test_is_sidechain_increments_counter(self):
706690
path = _write_jsonl(
707691
[
@@ -732,6 +716,7 @@ def test_file_history_snapshot_timestamp(self):
732716
s = parse_session(path)
733717
assert s["metadata"]["first_timestamp"] == "2026-01-02T12:00:00Z"
734718
assert s["metadata"]["last_timestamp"] == "2026-01-02T12:00:00Z"
719+
assert len(s["messages"]) == 0
735720
finally:
736721
os.unlink(path)
737722

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
@@ -25,6 +25,7 @@
2525
__all__ = ["parse_session", "quick_session_info"]
2626

2727
_HANDLED_ENTRY_TYPES = frozenset({"user", "assistant", "system", "progress"})
28+
_SKIP_ENTRY_TYPES = frozenset({"file-history-snapshot"})
2829
_VALID_ROLES = frozenset(get_args(RoleLiteral))
2930

3031

@@ -154,7 +155,7 @@ def parse_session(filepath: str) -> SessionDict:
154155
_process_progress(entry, messages)
155156
elif entry_type:
156157
type_str = entry_type if isinstance(entry_type, str) else str(entry_type)
157-
if type_str not in _HANDLED_ENTRY_TYPES:
158+
if type_str not in _HANDLED_ENTRY_TYPES and type_str not in _SKIP_ENTRY_TYPES:
158159
messages.append(_fallback_message(entry, _coerce_role(type_str)))
159160

160161
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)