From 286e858abe053466bc484e1871cb7f3f6dd26ebd Mon Sep 17 00:00:00 2001 From: Yunare Maia Date: Mon, 7 Sep 2026 16:44:43 +0000 Subject: [PATCH] fix(parse): guard _ts against non-string timestamp values A numeric timestamp (e.g. 1718000000 instead of ISO 8601 string) in a malformed transcript crashed _ts with AttributeError when calling value.replace(). Guard with isinstance check so non-string values return None instead. Closes #20 --- agentrace/parse.py | 2 ++ tests/test_agentrace.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/agentrace/parse.py b/agentrace/parse.py index 162e1ab..d921b0a 100644 --- a/agentrace/parse.py +++ b/agentrace/parse.py @@ -72,6 +72,8 @@ def find_sessions(root: Path | None = None) -> list[Path]: def _ts(value: str | None) -> datetime | None: + if not isinstance(value, str): + return None if not value: return None try: diff --git a/tests/test_agentrace.py b/tests/test_agentrace.py index d013eee..48dca39 100644 --- a/tests/test_agentrace.py +++ b/tests/test_agentrace.py @@ -146,6 +146,36 @@ def test_non_agent_tools_are_ignored(tmp_path): assert parse_session(p).runs == [] +def test_non_string_timestamp_does_not_crash(tmp_path): + """A numeric timestamp (malformed transcript) should not crash _ts.""" + p = tmp_path / "s.jsonl" + p.write_text( + json.dumps({ + "type": "assistant", + "timestamp": 1718000000, + "message": { + "content": [ + {"type": "tool_use", "id": "t1", "name": "Agent", "input": {"prompt": "go"}} + ] + }, + }) + + "\n" + + json.dumps({ + "type": "user", + "timestamp": 1718000600, + "message": { + "content": [ + {"type": "tool_result", "tool_use_id": "t1", "content": "done"} + ] + }, + }) + ) + s = parse_session(p) + assert len(s.runs) == 1 + assert s.runs[0].started_at is None + assert s.runs[0].ended_at is None + + def test_tool_use_without_id_is_skipped(tmp_path): """A tool_use block missing an id should be skipped rather than crashing with KeyError.""" p = tmp_path / "s.jsonl"