From 299bcf491b3bcc878a3c66e00fc065644a7a27ee Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Wed, 22 Jul 2026 00:24:01 -0700 Subject: [PATCH] fix(cron): skip null runHistory elements when loading jobs.json Null entries in state.runHistory raised TypeError and quarantined the store. Skip non-dict elements like LocalTrigger.from_dict already does. --- nanobot/cron/types.py | 1 + tests/cron/test_cron_service.py | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/nanobot/cron/types.py b/nanobot/cron/types.py index 18700765..89a2d541 100644 --- a/nanobot/cron/types.py +++ b/nanobot/cron/types.py @@ -118,6 +118,7 @@ class CronJobState: if isinstance(record, CronRunRecord) else CronRunRecord.from_store_dict(record) for record in history + if isinstance(record, (dict, CronRunRecord)) ], ) diff --git a/tests/cron/test_cron_service.py b/tests/cron/test_cron_service.py index 772884bd..0d91d6a9 100644 --- a/tests/cron/test_cron_service.py +++ b/tests/cron/test_cron_service.py @@ -1086,3 +1086,42 @@ def test_load_jobs_accepts_null_run_history_ms(tmp_path) -> None: assert jobs[0].state.run_history[0].status == "ok" assert jobs[0].created_at_ms == 0 assert jobs[0].updated_at_ms == 0 + + +def test_load_jobs_skips_null_run_history_elements(tmp_path) -> None: + """Null runHistory elements must be skipped like LocalTrigger.from_dict.""" + store_path = tmp_path / "cron" / "jobs.json" + store_path.parent.mkdir(parents=True) + store_path.write_text( + json.dumps( + { + "version": 1, + "jobs": [ + { + "id": "j1", + "name": "t", + "enabled": True, + "schedule": {"kind": "every", "everyMs": 60_000}, + "payload": { + "kind": "agent_turn", + "message": "hi", + "sessionKey": "websocket:chat-1", + }, + "state": { + "runHistory": [ + None, + {"runAtMs": 1, "status": "ok", "durationMs": 2}, + ], + }, + } + ], + } + ), + encoding="utf-8", + ) + + jobs, _version = CronService(store_path)._load_jobs() + assert jobs is not None + assert len(jobs[0].state.run_history) == 1 + assert jobs[0].state.run_history[0].run_at_ms == 1 + assert jobs[0].state.run_history[0].status == "ok"