From f572c7b57df98be45d65d3ba01b7456aafb1fa86 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 20 Jul 2026 10:56:37 +0200 Subject: [PATCH 01/15] fix(tracing): Stop setting NoOpSpan on scope --- sentry_sdk/tracing.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 1790e13fbf..3aa378ef55 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1270,6 +1270,14 @@ class NoOpSpan(Span): def __repr__(self) -> str: return "<%s>" % self.__class__.__name__ + def __enter__(self) -> "Span": + return self + + def __exit__( + self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]" + ) -> None: + return None + @property def containing_transaction(self) -> "Optional[Transaction]": return None From 29602f08556cc7be21a638077688e1bc8d2e10d3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 20 Jul 2026 11:14:26 +0200 Subject: [PATCH 02/15] . --- sentry_sdk/tracing.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 3aa378ef55..77a7c9292d 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -386,6 +386,9 @@ def __repr__(self) -> str: ) def __enter__(self) -> "Span": + if has_span_streaming_enabled(sentry_sdk.get_client().options): + return self + scope = self.scope or sentry_sdk.get_current_scope() old_span = scope.span scope.span = self @@ -395,6 +398,9 @@ def __enter__(self) -> "Span": def __exit__( self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]" ) -> None: + if has_span_streaming_enabled(sentry_sdk.get_client().options): + return None + if value is not None and should_be_treated_as_error(ty, value): self.set_status(SPANSTATUS.INTERNAL_ERROR) @@ -1270,14 +1276,6 @@ class NoOpSpan(Span): def __repr__(self) -> str: return "<%s>" % self.__class__.__name__ - def __enter__(self) -> "Span": - return self - - def __exit__( - self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]" - ) -> None: - return None - @property def containing_transaction(self) -> "Optional[Transaction]": return None @@ -1478,6 +1476,7 @@ def calculate_interest_rate(amount, rate, years): EnvironHeaders, _generate_sample_rand, extract_sentrytrace_data, + has_span_streaming_enabled, has_tracing_enabled, maybe_create_breadcrumbs_from_span, ) From 8eea1134211311ce8349053e7fe672015063f3c0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 15:37:20 +0200 Subject: [PATCH 03/15] close client on test --- tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 1f05f0eda6..83ba72f272 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -303,6 +303,9 @@ def inner(*a, **kw): sentry_sdk.get_current_scope().set_client(None) yield inner finally: + current = sentry_sdk.get_global_scope().client + if current is not None: + current.close() sentry_sdk.get_global_scope().set_client(old_client) From 19cd4d7f7c7c03cf71ad6fda1a189240c9a29ff4 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 15:53:04 +0200 Subject: [PATCH 04/15] . --- tests/test_logs.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test_logs.py b/tests/test_logs.py index ebd7e7f969..0d3af36d8c 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -835,7 +835,17 @@ def test_log_batcher_lock_reset_in_child_after_fork(sentry_init): original_lock = batcher._lock original_lock.acquire() - batcher._buffer.append(object()) + batcher._buffer.append( + { + "severity_text": "info", + "severity_number": 9, + "body": "fork-reset sentinel", + "attributes": {}, + "time_unix_nano": 0, + "trace_id": None, + "span_id": None, + } + ) batcher._active.flag = True batcher._flush_event.set() batcher._running = False From 7ee6aba8d05b524f033c5cb0513cc02e6a9ccb59 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 15:59:59 +0200 Subject: [PATCH 05/15] fix --- tests/conftest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 83ba72f272..ac0ea11d28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -287,9 +287,12 @@ def inner(identifier): @pytest.fixture def sentry_init(request): + clients = [] + def inner(*a, **kw): kw.setdefault("transport", TestTransport()) client = sentry_sdk.Client(*a, **kw) + clients.append(client) sentry_sdk.get_global_scope().set_client(client) if request.node.get_closest_marker("forked"): @@ -303,9 +306,8 @@ def inner(*a, **kw): sentry_sdk.get_current_scope().set_client(None) yield inner finally: - current = sentry_sdk.get_global_scope().client - if current is not None: - current.close() + for client in reversed(clients): + client.close() sentry_sdk.get_global_scope().set_client(old_client) From c6165db21e38407ee87911ddce2e2199fa2d97ea Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 16:00:56 +0200 Subject: [PATCH 06/15] . --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index ac0ea11d28..26333c6dee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -306,7 +306,7 @@ def inner(*a, **kw): sentry_sdk.get_current_scope().set_client(None) yield inner finally: - for client in reversed(clients): + for client in clients: client.close() sentry_sdk.get_global_scope().set_client(old_client) From 6f049c23b881a868e86cd8586bda6f0275916a99 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 16:13:25 +0200 Subject: [PATCH 07/15] add cleanup in forksafe fixture --- tests/conftest.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 26333c6dee..1b309119d4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -503,6 +503,12 @@ def flush(timeout=None, callback=None): items_w.write(json.dumps(telemetry).encode("utf-8") + b"\n") items_w.write(b"flush\n") + def cleanup(): + test_client.flush = real_flush + test_client.transport.capture_envelope = old_capture_envelope + + request.addfinalizer(cleanup) + monkeypatch.setattr(test_client.transport, "capture_envelope", append) monkeypatch.setattr(test_client, "flush", flush) From aa57be3d9615406ae9fe5c8168c4029b16e6d6c8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 16:17:27 +0200 Subject: [PATCH 08/15] add cleanup --- tests/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 1b309119d4..97d604e3dd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -457,6 +457,11 @@ def append(envelope): def flush(timeout=None, callback=None): events_w.write(b"flush\n") + def cleanup(): + test_client.transport.capture_envelope = old_capture_envelope + + request.addfinalizer(cleanup) + monkeypatch.setattr(test_client.transport, "capture_envelope", append) monkeypatch.setattr(test_client, "flush", flush) From acd48d97ecc98d0c3483fa419c886e2b5589f768 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 16:18:43 +0200 Subject: [PATCH 09/15] cleanup --- tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 97d604e3dd..7cf8eed31d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -454,10 +454,13 @@ def append(envelope): events_w.write(b"\n") return old_capture_envelope(envelope) + real_flush = test_client.flush + def flush(timeout=None, callback=None): events_w.write(b"flush\n") def cleanup(): + test_client.flush = real_flush test_client.transport.capture_envelope = old_capture_envelope request.addfinalizer(cleanup) From 126294c00013a963a631abdcb2893b850dcf4326 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 16:42:34 +0200 Subject: [PATCH 10/15] fix --- sentry_sdk/tracing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 77a7c9292d..475dc86ca9 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -398,8 +398,8 @@ def __enter__(self) -> "Span": def __exit__( self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]" ) -> None: - if has_span_streaming_enabled(sentry_sdk.get_client().options): - return None + if not hasattr(self, "_context_manager_state"): + return if value is not None and should_be_treated_as_error(ty, value): self.set_status(SPANSTATUS.INTERNAL_ERROR) From bdf3ef5da32c1bff84059c91fd6e96de9cc4e9f3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 16:42:58 +0200 Subject: [PATCH 11/15] revert conftest changes --- tests/conftest.py | 19 ------------------- tests/test_logs.py | 12 +----------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7cf8eed31d..1f05f0eda6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -287,12 +287,9 @@ def inner(identifier): @pytest.fixture def sentry_init(request): - clients = [] - def inner(*a, **kw): kw.setdefault("transport", TestTransport()) client = sentry_sdk.Client(*a, **kw) - clients.append(client) sentry_sdk.get_global_scope().set_client(client) if request.node.get_closest_marker("forked"): @@ -306,8 +303,6 @@ def inner(*a, **kw): sentry_sdk.get_current_scope().set_client(None) yield inner finally: - for client in clients: - client.close() sentry_sdk.get_global_scope().set_client(old_client) @@ -454,17 +449,9 @@ def append(envelope): events_w.write(b"\n") return old_capture_envelope(envelope) - real_flush = test_client.flush - def flush(timeout=None, callback=None): events_w.write(b"flush\n") - def cleanup(): - test_client.flush = real_flush - test_client.transport.capture_envelope = old_capture_envelope - - request.addfinalizer(cleanup) - monkeypatch.setattr(test_client.transport, "capture_envelope", append) monkeypatch.setattr(test_client, "flush", flush) @@ -511,12 +498,6 @@ def flush(timeout=None, callback=None): items_w.write(json.dumps(telemetry).encode("utf-8") + b"\n") items_w.write(b"flush\n") - def cleanup(): - test_client.flush = real_flush - test_client.transport.capture_envelope = old_capture_envelope - - request.addfinalizer(cleanup) - monkeypatch.setattr(test_client.transport, "capture_envelope", append) monkeypatch.setattr(test_client, "flush", flush) diff --git a/tests/test_logs.py b/tests/test_logs.py index 0d3af36d8c..ebd7e7f969 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -835,17 +835,7 @@ def test_log_batcher_lock_reset_in_child_after_fork(sentry_init): original_lock = batcher._lock original_lock.acquire() - batcher._buffer.append( - { - "severity_text": "info", - "severity_number": 9, - "body": "fork-reset sentinel", - "attributes": {}, - "time_unix_nano": 0, - "trace_id": None, - "span_id": None, - } - ) + batcher._buffer.append(object()) batcher._active.flag = True batcher._flush_event.set() batcher._running = False From 62f2604102320fd335949509ff0a8c4fd12d542b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 17:01:40 +0200 Subject: [PATCH 12/15] use explicit sentinel --- sentry_sdk/tracing.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 475dc86ca9..93a8a2e227 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -222,6 +222,9 @@ def add(self, span: "Span") -> None: self.spans.append(span) +_NOOP_CONTEXT = object() + + class Span: """A span holds timing information of a block of code. Spans can have multiple child spans thus forming a span tree. @@ -387,6 +390,7 @@ def __repr__(self) -> str: def __enter__(self) -> "Span": if has_span_streaming_enabled(sentry_sdk.get_client().options): + self._context_manager_state = _NOOP_CONTEXT return self scope = self.scope or sentry_sdk.get_current_scope() @@ -398,7 +402,9 @@ def __enter__(self) -> "Span": def __exit__( self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]" ) -> None: - if not hasattr(self, "_context_manager_state"): + state = getattr(self, "_context_manager_state", None) + if state is _NOOP_CONTEXT: + del self._context_manager_state return if value is not None and should_be_treated_as_error(ty, value): From 9280a0345ee0fe3666a28b5a979373dc75735269 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 17:06:37 +0200 Subject: [PATCH 13/15] mypy --- sentry_sdk/tracing.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 93a8a2e227..ce8903f926 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -222,9 +222,6 @@ def add(self, span: "Span") -> None: self.spans.append(span) -_NOOP_CONTEXT = object() - - class Span: """A span holds timing information of a block of code. Spans can have multiple child spans thus forming a span tree. @@ -281,6 +278,8 @@ class Span: "_flags_capacity", ) + _context_manager_state: "Optional[Tuple[sentry_sdk.Scope, Optional[Span]]]" + def __init__( self, trace_id: "Optional[str]" = None, @@ -390,7 +389,7 @@ def __repr__(self) -> str: def __enter__(self) -> "Span": if has_span_streaming_enabled(sentry_sdk.get_client().options): - self._context_manager_state = _NOOP_CONTEXT + self._context_manager_state = None # early return sentinel return self scope = self.scope or sentry_sdk.get_current_scope() @@ -402,8 +401,10 @@ def __enter__(self) -> "Span": def __exit__( self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]" ) -> None: - state = getattr(self, "_context_manager_state", None) - if state is _NOOP_CONTEXT: + if ( + hasattr(self, "_context_manager_state") + and self._context_manager_state is None + ): del self._context_manager_state return From fc65f09343689d5162f69684a3b53d085028d249 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 17:13:42 +0200 Subject: [PATCH 14/15] add cast --- sentry_sdk/tracing.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index ce8903f926..2789e03dcf 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -2,7 +2,7 @@ import warnings from datetime import datetime, timedelta, timezone from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import sentry_sdk from sentry_sdk.consts import INSTRUMENTER, SPANDATA, SPANSTATUS, SPANTEMPLATE @@ -412,7 +412,10 @@ def __exit__( self.set_status(SPANSTATUS.INTERNAL_ERROR) with capture_internal_exceptions(): - scope, old_span = self._context_manager_state + scope, old_span = cast( + "Tuple[sentry_sdk.Scope, Optional[Span]]", + self._context_manager_state, + ) del self._context_manager_state self.finish(scope) scope.span = old_span From 02ee3d1e558ee26796584e4ebf1d54d81a87365b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 21 Jul 2026 17:16:03 +0200 Subject: [PATCH 15/15] remove declaration --- sentry_sdk/tracing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 2789e03dcf..291e1649a4 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -278,8 +278,6 @@ class Span: "_flags_capacity", ) - _context_manager_state: "Optional[Tuple[sentry_sdk.Scope, Optional[Span]]]" - def __init__( self, trace_id: "Optional[str]" = None,