diff --git a/packages/wf-brainscales2-demos/package.py b/packages/wf-brainscales2-demos/package.py
index 59c505af874d496e07e40a15ed4a4b53e19e4dec..580fd5cecc6788fb274352cd253d875af900fef9 100644
--- a/packages/wf-brainscales2-demos/package.py
+++ b/packages/wf-brainscales2-demos/package.py
@@ -44,6 +44,7 @@ class WfBrainscales2Demos(Package):
         args = [
             "nbconvert",
             "--ExecutePreprocessor.kernel_name=python3",
+            "--ExecutePreprocessor.timeout=900",
             "--execute",
             "--to",
             "notebook",
@@ -53,16 +54,25 @@ class WfBrainscales2Demos(Package):
         ]
         try:
             # execute notebook and save
-            jupyter(*args)
+            jupyter(*args, output=str.split, error=str.split)
         except ProcessError as e:
-            # if the above fails, re-run notebook to produce output with error
-            jupyter(*(args+["--allow-errors"]))
+            # if the notebook execution fails, re-run notebook to produce output with error
+            # in case of a cell timeout, don't re-run
+            if "CellTimeoutError" not in e:
+                jupyter(*(args+["--allow-errors"]))
             raise
 
     def _run_notebooks(self, output_dir):
         mkdirp(output_dir)
+        # try to run all notebooks, then fail if there are errors
+        exceptions = []
         for fn in glob(join_path(prefix, "notebooks", "ts*.ipynb")) + glob(join_path(prefix, "notebooks", "tp*.ipynb")):
-            self._nbconvert(fn, join_path(output_dir, os.path.basename(fn)))
+            try:
+                self._nbconvert(fn, join_path(output_dir, os.path.basename(fn)))
+            except Exception as e:
+                exceptions.append(e)
+        if exceptions:
+            raise Exception("Errors during notebook execution")
 
     def _set_collab_things(self):
         # enable "EBRAINS lab" mode