diff --git a/src/memos/utils.py b/src/memos/utils.py index f7111f8ad..270c232e9 100644 --- a/src/memos/utils.py +++ b/src/memos/utils.py @@ -179,6 +179,9 @@ def wrapper(*args, **kwargs): if fallback is not None and callable(fallback): result = fallback(e, *args, **kwargs) return result + else: + # Re-raise the exception if no fallback is provided + raise finally: elapsed_ms = (time.perf_counter() - start) * 1000.0 diff --git a/tests/test_utils_timing.py b/tests/test_utils_timing.py index b4d5cb989..dc55be630 100644 --- a/tests/test_utils_timing.py +++ b/tests/test_utils_timing.py @@ -388,3 +388,34 @@ def parens(): assert bare() == 1 assert parens() == 2 + + def test_exception_without_fallback_should_propagate(self, caplog): + """Bug fix: exceptions should be re-raised when no fallback is provided.""" + + @timed_with_status + def failing_func(): + raise ValueError("test exception") + + with caplog.at_level(logging.INFO): + with pytest.raises(ValueError, match="test exception"): + failing_func() + + # The exception should propagate AND the failure should be logged + logs = _collect_timer_with_status_logs(caplog) + assert len(logs) == 1 + assert "status: FAILED" in logs[0] + assert "error_type: ValueError" in logs[0] + + def test_exception_with_none_fallback_should_propagate(self, caplog): + """Bug fix: explicit fallback=None should still re-raise exceptions.""" + + @timed_with_status(fallback=None) + def failing_func(): + raise RuntimeError("explicit none") + + with caplog.at_level(logging.INFO): + with pytest.raises(RuntimeError, match="explicit none"): + failing_func() + + logs = _collect_timer_with_status_logs(caplog) + assert "status: FAILED" in logs[0]