owenrusk.dev

errands

small jobs the runtime does for the studio.

git clone https://owenrusk.dev/errands.git

commit 510acc0b2dde401ac4b59e3c683a6eaa3b2b3f11
parent 8c1527bf3320bbfbd9927369ecd9bc0f7ddd1084
author Owen Rusk <owen@papermothgames.com>
date   2026-02-10 14:50:41 -0600
jobs: one job failing doesn't stop the rest
errands/jobs.py+5-1
tests/test_jobs.py+10-0
diff --git a/errands/jobs.py b/errands/jobs.py
index 35a4bba..0d6bfe8 100644
--- a/errands/jobs.py
+++ b/errands/jobs.py
@@ -42,7 +42,11 @@ def tick(now: datetime, last: dict[str, datetime], jobs: list[Job] | None = None
         if is_due(job, now, last.get(job.name)):
             last[job.name] = now
             log.info("%s", job.name)
-            job.run()
+            try:
+                job.run()
+            except Exception:
+                log.exception("%s failed", job.name)
+                continue
             ran.append(job.name)
     return ran
 
diff --git a/tests/test_jobs.py b/tests/test_jobs.py
index 1458778..353449c 100644
--- a/tests/test_jobs.py
+++ b/tests/test_jobs.py
@@ -33,3 +33,13 @@ class TickTest(unittest.TestCase):
         self.assertEqual(jobs.tick(at(9), last, [job]), ["x"])
         self.assertEqual(jobs.tick(at(9, 1), last, [job]), [])
         self.assertEqual(calls, [1])
+
+    def test_a_failure_doesnt_stop_the_rest(self):
+        def broken():
+            raise RuntimeError("down")
+
+        calls = []
+        jobs_ = [jobs.Job("broken", broken, every=5), jobs.Job("fine", lambda: calls.append(1), every=5)]
+        with self.assertLogs("errands", level="ERROR"):
+            self.assertEqual(jobs.tick(at(9), {}, jobs_), ["fine"])
+        self.assertEqual(calls, [1])