Skip to content

Commit 54a7b72

Browse files
committed
Gracefully handle corrupt or malformed run store files
- Catch OSError and JSONDecodeError in summarize() and return None instead of raising - Validate all events are dicts and run_id is a non-empty string before producing a RunSummary
1 parent ba7fbcc commit 54a7b72

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

teaagent/run_store.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,17 @@ def heartbeat_for_run(self, run_id: str) -> dict[str, Any]:
9191
}
9292

9393
def summarize(self, path: Path) -> Optional[RunSummary]:
94-
events = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
94+
try:
95+
events = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
96+
except (OSError, json.JSONDecodeError):
97+
return None
9598
if not events:
9699
return None
97-
run_id = events[0]["run_id"]
100+
if not all(isinstance(event, dict) for event in events):
101+
return None
102+
run_id = events[0].get("run_id")
103+
if not isinstance(run_id, str) or not run_id:
104+
return None
98105
task = ""
99106
status = "unknown"
100107
final_answer = None

tests/test_run_store.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,32 @@ def test_cli_lists_and_shows_runs(self) -> None:
7373
self.assertEqual(json.loads(list_output.getvalue())[0]["run_id"], "run-2")
7474
self.assertEqual(json.loads(show_output.getvalue())[1]["event_type"], "run_failed")
7575

76+
def test_list_runs_skips_corrupt_jsonl_files(self) -> None:
77+
with tempfile.TemporaryDirectory() as tmp:
78+
store = RunStore(tmp)
79+
audit = store.audit_logger()
80+
audit.record("run_started", "run-ok", task="demo")
81+
audit.record("run_completed", "run-ok", answer="ok", metadata={})
82+
store.logger_for_result(
83+
RunResult(run_id="run-ok", final_answer=FinalAnswer("ok"), iterations=1, tool_calls=0, status="completed"),
84+
audit,
85+
)
86+
(Path(tmp) / ".teaagent" / "runs" / "broken.jsonl").write_text("not json\n", encoding="utf-8")
87+
88+
summaries = store.list_runs()
89+
90+
self.assertEqual([summary.run_id for summary in summaries], ["run-ok"])
91+
92+
def test_list_runs_skips_records_without_run_id(self) -> None:
93+
with tempfile.TemporaryDirectory() as tmp:
94+
store = RunStore(tmp)
95+
(Path(tmp) / ".teaagent" / "runs" / "missing-id.jsonl").write_text(
96+
json.dumps({"event_type": "run_started", "payload": {"task": "x"}}) + "\n",
97+
encoding="utf-8",
98+
)
99+
100+
self.assertEqual(store.list_runs(), [])
101+
76102

77103
if __name__ == "__main__":
78104
unittest.main()

0 commit comments

Comments
 (0)