From ae55711f266f5922deaa672f196042b4f359946d Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 17 Jun 2025 09:17:13 +0200 Subject: [PATCH 1/2] Handle token reset LookupErrors as internal exceptions --- sentry_sdk/scope.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index f346569255..73bf43573e 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -1673,8 +1673,11 @@ def new_scope(): yield new_scope finally: - # restore original scope - _current_scope.reset(token) + try: + # restore original scope + _current_scope.reset(token) + except LookupError: + capture_internal_exception(sys.exc_info()) @contextmanager @@ -1708,8 +1711,11 @@ def use_scope(scope): yield scope finally: - # restore original scope - _current_scope.reset(token) + try: + # restore original scope + _current_scope.reset(token) + except LookupError: + capture_internal_exception(sys.exc_info()) @contextmanager @@ -1750,8 +1756,15 @@ def isolation_scope(): finally: # restore original scopes - _current_scope.reset(current_token) - _isolation_scope.reset(isolation_token) + try: + _current_scope.reset(current_token) + except LookupError: + capture_internal_exception(sys.exc_info()) + + try: + _isolation_scope.reset(isolation_token) + except LookupError: + capture_internal_exception(sys.exc_info()) @contextmanager @@ -1790,8 +1803,15 @@ def use_isolation_scope(isolation_scope): finally: # restore original scopes - _current_scope.reset(current_token) - _isolation_scope.reset(isolation_token) + try: + _current_scope.reset(current_token) + except LookupError: + capture_internal_exception(sys.exc_info()) + + try: + _isolation_scope.reset(isolation_token) + except LookupError: + capture_internal_exception(sys.exc_info()) def should_send_default_pii(): From 45aa46b8bce13158c42068dfe5b70b8450842ebc Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 17 Jun 2025 09:52:20 +0200 Subject: [PATCH 2/2] tests --- tests/test_scope.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/test_scope.py b/tests/test_scope.py index 9b16dc4344..e645d84234 100644 --- a/tests/test_scope.py +++ b/tests/test_scope.py @@ -905,3 +905,67 @@ def test_last_event_id_cleared(sentry_init): Scope.get_isolation_scope().clear() assert Scope.last_event_id() is None, "last_event_id should be cleared" + + +@pytest.mark.tests_internal_exceptions +@pytest.mark.parametrize( + "scope_manager", + [ + new_scope, + use_scope, + ], +) +def test_handle_lookup_error_on_token_reset_current_scope(scope_manager): + with mock.patch("sentry_sdk.scope.capture_internal_exception") as mock_capture: + with mock.patch("sentry_sdk.scope._current_scope") as mock_token_var: + mock_token_var.reset.side_effect = LookupError() + + mock_token = mock.Mock() + mock_token_var.set.return_value = mock_token + + try: + if scope_manager == use_scope: + with scope_manager(Scope()): + pass + else: + with scope_manager(): + pass + + except Exception: + pytest.fail("Context manager should handle LookupError gracefully") + + mock_capture.assert_called_once() + mock_token_var.reset.assert_called_once_with(mock_token) + + +@pytest.mark.tests_internal_exceptions +@pytest.mark.parametrize( + "scope_manager", + [ + isolation_scope, + use_isolation_scope, + ], +) +def test_handle_lookup_error_on_token_reset_isolation_scope(scope_manager): + with mock.patch("sentry_sdk.scope.capture_internal_exception") as mock_capture: + with mock.patch("sentry_sdk.scope._current_scope") as mock_current_scope: + with mock.patch( + "sentry_sdk.scope._isolation_scope" + ) as mock_isolation_scope: + mock_isolation_scope.reset.side_effect = LookupError() + mock_current_token = mock.Mock() + mock_current_scope.set.return_value = mock_current_token + + try: + if scope_manager == use_isolation_scope: + with scope_manager(Scope()): + pass + else: + with scope_manager(): + pass + + except Exception: + pytest.fail("Context manager should handle LookupError gracefully") + + mock_capture.assert_called_once() + mock_current_scope.reset.assert_called_once_with(mock_current_token)