diff --git a/run-exp.py b/run-exp.py
index f9072d7ebcb344adca06619b7cbde6af72d4d76a..56004ee2fc638da523e8c683af4f32582b542efc 100755
--- a/run-exp.py
+++ b/run-exp.py
@@ -60,11 +60,12 @@ class Variable:
 
 
 class Task:
-    def __init__(self, name, work_dir, base_command, parameters):
+    def __init__(self, name, work_dir, base_command, parameters, timeout):
         self._name = name
         self._work_dir = work_dir
         self._base_command = base_command
         self._parameters = parameters
+        self._timeout = timeout
 
     @property
     def name(self):
@@ -74,8 +75,12 @@ class Task:
     def work_dir(self):
         return self._work_dir
 
-    def run(self, variables):
-        args = [self._base_command]
+    def run(self, variables, global_timeout, dry_run):
+        if dry_run:
+            args = ["echo"]
+        else:
+            args = []
+        args.append(self._base_command)
 
         for p in self._parameters:
             if not isinstance(p, dict):
@@ -102,13 +107,18 @@ class Task:
         # print(f"pushd {self._work_dir}")
         # print(" ".join(args))
         # print("popd")
+        timeout = self._timeout
+        if timeout is not None and global_timeout is not None:
+            timeout = min(timeout, global_timeout)
+        elif global_timeout is not None:
+            timeout = global_timeout
 
         try:
             subprocess.run(
                 " ".join(args),
                 shell = True,
                 cwd = self._work_dir,
-                timeout = 3600,  # FIXME
+                timeout = timeout,
             )
 
             return False
@@ -128,6 +138,8 @@ class Task:
 
         parameters = data.get("parameters", [])
 
+        timeout = data.get("timeout", None)
+
         if "work-dir" in data:
             work_dir = pathlib.Path(data["work-dir"]).expanduser()
         else:
@@ -135,7 +147,7 @@ class Task:
 
         assert work_dir.exists(), f"Working directory \"{work_dir}\" for task \"{name}\" does not exist!"
 
-        return cls(name, work_dir, base_command, parameters)
+        return cls(name, work_dir, base_command, parameters, timeout)
 
 
 class Config:
@@ -177,7 +189,9 @@ class Config:
 @click.command()
 @click.argument("config_file", type = click.Path(
     exists = True, file_okay = True, dir_okay = False, readable = True, path_type = pathlib.Path))
-def main(config_file):
+@click.option("--global-timeout", type = click.IntRange(1), default = 7200)
+@click.option("--dry-run", is_flag = True)
+def main(config_file, global_timeout, dry_run):
     c = Config.from_file(config_file)
 
     print(f"> Running experiment \"{c.name}\"")
@@ -202,7 +216,7 @@ def main(config_file):
 
             for task in c.tasks:
                 print(f"Running task {task.name}")
-                timed_out = task.run(variables)
+                timed_out = task.run(variables, global_timeout, dry_run)
             print()
 
             if timed_out:  # repetitions are useless if tasks time out