diff --git a/logexp/experiment.py b/logexp/experiment.py
index 236b647..68a010c 100644
--- a/logexp/experiment.py
+++ b/logexp/experiment.py
@@ -19,7 +19,9 @@ class Experiment:
         experiment = Experiment._experiments[cls].get(name)
 
         if experiment is None:
-            raise error.ExperimentNotFoundError
+            raise error.ExperimentNotFoundError(
+                f"available experiments: {list(Experiment._experiments[cls])}"
+            )
 
         return experiment
 
diff --git a/logexp/utils/capture.py b/logexp/utils/capture.py
index 9a71f21..e00b999 100644
--- a/logexp/utils/capture.py
+++ b/logexp/utils/capture.py
@@ -1,68 +1,55 @@
 from __future__ import annotations
+import typing as tp
 
 import contextlib
-import os
-import subprocess
+import io
 import sys
-import tempfile
-import traceback
 
+class TeeingStreamProxy:
+    def __init__(self, stream: tp.IO, out: tp.IO):
+        self._stream = stream
+        self._out = out
 
-@contextlib.contextmanager
-def capture():
-    with tempfile.TemporaryDirectory() as temppath:
-        stdout_path = os.path.join(temppath, "stdout.txt")
-        stderr_path = os.path.join(temppath, "stderr.txt")
+    def __getattr__(self, name: str):
+        return getattr(self._stream, name)
+
+    def write(self, data):
+        self._stream.write(data)
+        self._out.write(data)
 
-        original_stdout_fd = 1
-        original_stderr_fd = 2
-        saved_stdout_fd = os.dup(original_stdout_fd)
-        saved_stderr_fd = os.dup(original_stderr_fd)
+    def flush(self):
+        self._stream.flush()
+        self._out.flush()
 
-        tee_out = subprocess.Popen(
-            ["tee", "-a", stdout_path],
-            start_new_session=True,
-            stdin=subprocess.PIPE,
-            stdout=1
-        )
-        tee_err = subprocess.Popen(
-            ["tee", "-a", stderr_path],
-            start_new_session=True,
-            stdin=subprocess.PIPE,
-            stdout=2
-        )
 
-        os.dup2(tee_out.stdin.fileno(), original_stdout_fd)
-        os.dup2(tee_err.stdin.fileno(), original_stderr_fd)
+@contextlib.contextmanager
+def capture():
+    stdout_buffer = io.StringIO()
+    stderr_buffer = io.StringIO()
 
-        capture_result = {
-            "stdout": "",
-            "stderr": "",
-        }
+    orig_stdout, orig_stderr = sys.stdout, sys.stderr
 
-        try:
-            yield capture_result
-        except Exception as e:
-            sys.stderr.write(traceback.format_exc())
-            raise e
-        finally:
-            sys.stdout.flush()
-            sys.stderr.flush()
+    sys.stdout.flush()
+    sys.stderr.flush()
 
-            tee_out.stdin.close()
-            tee_err.stdin.close()
+    sys.stdout = TeeingStreamProxy(sys.stdout, stdout_buffer)
+    sys.stderr = TeeingStreamProxy(sys.stderr, stderr_buffer)
 
-            os.dup2(saved_stdout_fd, original_stdout_fd)
-            os.dup2(saved_stderr_fd, original_stderr_fd)
+    out = {
+        "stdout": "",
+        "stderr": "",
+    }
 
-            tee_out.wait(timeout=1)
-            tee_err.wait(timeout=1)
+    try:
+        yield out
+    finally:
+        sys.stdout.flush()
+        sys.stderr.flush()
 
-            os.close(saved_stdout_fd)
-            os.close(saved_stderr_fd)
+        out["stdout"] = stdout_buffer.getvalue()
+        out["stderr"] = stderr_buffer.getvalue()
 
-            with open(stdout_path) as f:
-                capture_result["stdout"] = f.read()
+        stdout_buffer.close()
+        stderr_buffer.close()
 
-            with open(stderr_path) as f:
-                capture_result["stderr"] = f.read()
+        sys.stdout, sys.stderr = orig_stdout, orig_stderr