From 80803228fcd88a6c9f7c7551f3266e8e33c9ddc8 Mon Sep 17 00:00:00 2001 From: Tony Le Date: Wed, 25 Feb 2026 09:53:52 -0500 Subject: [PATCH] fix(spans): handle null span attributes --- src/sentry/spans/buffer.py | 4 +++- tests/sentry/spans/test_buffer.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/sentry/spans/buffer.py b/src/sentry/spans/buffer.py index eefee39ac856b0..67045208223d81 100644 --- a/src/sentry/spans/buffer.py +++ b/src/sentry/spans/buffer.py @@ -529,7 +529,9 @@ def flush_segments(self, now: int) -> dict[SegmentKey, FlushedSegment]: span = orjson.loads(payload) if not attribute_value(span, "sentry.segment.id"): - span.setdefault("attributes", {})["sentry.segment.id"] = { + if not isinstance(span.get("attributes"), dict): + span["attributes"] = {} + span["attributes"]["sentry.segment.id"] = { "type": "string", "value": segment_span_id, } diff --git a/tests/sentry/spans/test_buffer.py b/tests/sentry/spans/test_buffer.py index 60752dc3492a87..16bd3e2655d78f 100644 --- a/tests/sentry/spans/test_buffer.py +++ b/tests/sentry/spans/test_buffer.py @@ -348,6 +348,27 @@ def test_observability_metrics_parent_span_already_oversized( ) +def test_flush_segments_with_null_attributes(buffer: SpansBuffer) -> None: + spans = [ + Span( + payload=orjson.dumps({"span_id": "b" * 16, "attributes": None}), + trace_id="a" * 32, + span_id="b" * 16, + parent_span_id=None, + segment_id=None, + is_segment_span=True, + project_id=1, + end_timestamp=1700000000.0, + ), + ] + + process_spans(spans, buffer, now=0) + + rv = buffer.flush_segments(now=11) + segment = rv[_segment_id(1, "a" * 32, "b" * 16)] + assert segment.spans[0].payload["attributes"]["sentry.segment.id"]["value"] == "b" * 16 + + @pytest.mark.parametrize( "spans", list(