diff a/bot/src/test/java/org/openjdk/skara/bot/BotRunnerTests.java b/bot/src/test/java/org/openjdk/skara/bot/BotRunnerTests.java
--- a/bot/src/test/java/org/openjdk/skara/bot/BotRunnerTests.java
+++ b/bot/src/test/java/org/openjdk/skara/bot/BotRunnerTests.java
@@ -20,21 +20,23 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package org.openjdk.skara.bot;
 
+import org.openjdk.skara.json.JSON;
+
 import org.junit.jupiter.api.*;
-import org.openjdk.skara.host.HostedRepository;
-import org.openjdk.skara.json.*;
 
-import java.nio.file.*;
+import java.nio.file.Path;
 import java.time.Duration;
 import java.util.*;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.*;
 import java.util.function.Supplier;
 import java.util.logging.*;
 
+import static org.junit.jupiter.api.Assertions.*;
+
 class TestWorkItem implements WorkItem {
     private final ConcurrencyCheck concurrencyCheck;
     private final String description;
     boolean hasRun = false;
 
@@ -73,16 +75,40 @@
     TestWorkItemChild(ConcurrencyCheck concurrencyCheck, String description) {
         super(concurrencyCheck, description);
     }
 }
 
+class TestBlockedWorkItem implements WorkItem {
+    private final CountDownLatch countDownLatch;
+
+    TestBlockedWorkItem(CountDownLatch countDownLatch) {
+        this.countDownLatch = countDownLatch;
+    }
+
+    @Override
+    public boolean concurrentWith(WorkItem other) {
+        return false;
+    }
+
+    @Override
+    public void run(Path scratchPath) {
+        System.out.println("Starting to wait...");;
+        try {
+            countDownLatch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("Done waiting");
+    }
+}
+
 class TestBot implements Bot {
 
     private final List<WorkItem> items;
     private final Supplier<List<WorkItem>> itemSupplier;
 
-    TestBot(TestWorkItem... items) {
+    TestBot(WorkItem... items) {
         this.items = Arrays.asList(items);
         itemSupplier = null;
     }
 
     TestBot(Supplier<List<WorkItem>> itemSupplier) {
@@ -120,21 +146,29 @@
         } catch (ConfigurationError configurationError) {
             throw new RuntimeException(configurationError);
         }
     }
 
+    private BotRunnerConfiguration config(String json) {
+        var config = JSON.parse(json).asObject();
+        try {
+            return BotRunnerConfiguration.parse(config);
+        } catch (ConfigurationError configurationError) {
+            throw new RuntimeException(configurationError);
+        }
+    }
     @Test
     void simpleConcurrent() throws TimeoutException {
         var item1 = new TestWorkItem(i -> true, "Item 1");
         var item2 = new TestWorkItem(i -> true, "Item 2");
         var bot = new TestBot(item1, item2);
         var runner = new BotRunner(config(), List.of(bot));
 
         runner.runOnce(Duration.ofSeconds(10));
 
-        Assertions.assertTrue(item1.hasRun);
-        Assertions.assertTrue(item2.hasRun);
+        assertTrue(item1.hasRun);
+        assertTrue(item2.hasRun);
     }
 
     @Test
     void simpleSerial() throws TimeoutException {
         var item1 = new TestWorkItem(i -> false, "Item 1");
@@ -142,12 +176,12 @@
         var bot = new TestBot(item1, item2);
         var runner = new BotRunner(config(), List.of(bot));
 
         runner.runOnce(Duration.ofSeconds(10));
 
-        Assertions.assertTrue(item1.hasRun);
-        Assertions.assertTrue(item2.hasRun);
+        assertTrue(item1.hasRun);
+        assertTrue(item2.hasRun);
     }
 
     @Test
     void moreItemsThanScratchPaths() throws TimeoutException {
         List<TestWorkItem> items = new LinkedList<>();
@@ -158,11 +192,11 @@
         var runner = new BotRunner(config(), List.of(bot));
 
         runner.runOnce(Duration.ofSeconds(10));
 
         for (var item : items) {
-            Assertions.assertTrue(item.hasRun);
+            assertTrue(item.hasRun);
         }
     }
 
     static class ThrowingItemProvider {
         private final List<WorkItem> items;
@@ -193,12 +227,12 @@
         new BotRunner(config(), List.of(bot)).runOnce(Duration.ofSeconds(10));
         Assertions.assertFalse(item1.hasRun);
         Assertions.assertFalse(item2.hasRun);
 
         new BotRunner(config(), List.of(bot)).runOnce(Duration.ofSeconds(10));
-        Assertions.assertTrue(item1.hasRun);
-        Assertions.assertTrue(item2.hasRun);
+        assertTrue(item1.hasRun);
+        assertTrue(item2.hasRun);
     }
 
     @Test
     void discardAdditionalBlockedItems() throws TimeoutException {
         var item1 = new TestWorkItem(i -> false, "Item 1");
@@ -208,14 +242,14 @@
         var bot = new TestBot(item1, item2, item3, item4);
         var runner = new BotRunner(config(), List.of(bot));
 
         runner.runOnce(Duration.ofSeconds(10));
 
-        Assertions.assertTrue(item1.hasRun);
+        assertTrue(item1.hasRun);
         Assertions.assertFalse(item2.hasRun);
         Assertions.assertFalse(item3.hasRun);
-        Assertions.assertTrue(item4.hasRun);
+        assertTrue(item4.hasRun);
     }
 
     @Test
     void dontDiscardDifferentBlockedItems() throws TimeoutException {
         var item1 = new TestWorkItem(i -> false, "Item 1");
@@ -228,14 +262,46 @@
         var bot = new TestBot(item1, item2, item3, item4, item5, item6, item7);
         var runner = new BotRunner(config(), List.of(bot));
 
         runner.runOnce(Duration.ofSeconds(10));
 
-        Assertions.assertTrue(item1.hasRun);
+        assertTrue(item1.hasRun);
         Assertions.assertFalse(item2.hasRun);
         Assertions.assertFalse(item3.hasRun);
-        Assertions.assertTrue(item4.hasRun);
+        assertTrue(item4.hasRun);
         Assertions.assertFalse(item5.hasRun);
         Assertions.assertFalse(item6.hasRun);
-        Assertions.assertTrue(item7.hasRun);
+        assertTrue(item7.hasRun);
+    }
+
+    @Test
+    void watchdogTrigger() throws TimeoutException {
+        var countdownLatch = new CountDownLatch(1);
+        var item = new TestBlockedWorkItem(countdownLatch);
+        var bot = new TestBot(item);
+        var runner = new BotRunner(config("{ \"runner\": { \"watchdog\": \"PT0.01S\" } }"), List.of(bot));
+
+        var errors = new ArrayList<String>();
+        var log = Logger.getLogger("org.openjdk.skara.bot");
+        log.addHandler(new Handler() {
+            @Override
+            public void publish(LogRecord record) {
+                if (record.getLevel().equals(Level.SEVERE)) {
+                    errors.add(record.getMessage());
+                }
+            }
+
+            @Override
+            public void flush() {
+            }
+
+            @Override
+            public void close() throws SecurityException {
+            }
+        });
+
+        assertThrows(TimeoutException.class, () -> runner.runOnce(Duration.ofMillis(100)));
+        assertTrue(errors.size() > 0);
+        assertTrue(errors.size() <= 10);
+        countdownLatch.countDown();
     }
 }
