Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sentry_sdk/_span_batcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ def _to_transport_format(item: "StreamedSpan") -> "Any":
"start_timestamp": item._start_timestamp.timestamp(),
}

if item._timestamp:
res["end_timestamp"] = item._timestamp.timestamp()
if item._end_timestamp:
res["end_timestamp"] = item._end_timestamp.timestamp()

if item._parent_span_id:
res["parent_span_id"] = item._parent_span_id
Expand Down
20 changes: 10 additions & 10 deletions sentry_sdk/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class StreamedSpan:
"_parent_sampled",
"_start_timestamp",
"_start_timestamp_monotonic_ns",
"_timestamp",
"_end_timestamp",
"_status",
"_scope",
"_previous_span_on_scope",
Expand Down Expand Up @@ -291,7 +291,7 @@ def __init__(
self._sample_rate = sample_rate

self._start_timestamp = datetime.now(timezone.utc)
self._timestamp: "Optional[datetime]" = None
self._end_timestamp: "Optional[datetime]" = None
Comment thread
sentrivana marked this conversation as resolved.
Comment thread
sentry-warden[bot] marked this conversation as resolved.

# profiling depends on this value and requires that
# it is measured in nanoseconds
Expand Down Expand Up @@ -327,7 +327,7 @@ def __enter__(self) -> "StreamedSpan":
def __exit__(
self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
) -> None:
if self._timestamp is not None:
if self._end_timestamp is not None:
# This span is already finished, ignore
return

Expand Down Expand Up @@ -361,7 +361,7 @@ def _start(self) -> None:
self._previous_span_on_scope = old_span

def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None:
if self._timestamp is not None:
if self._end_timestamp is not None:
# This span is already finished, ignore.
return

Expand Down Expand Up @@ -392,15 +392,15 @@ def _end(self, end_timestamp: "Optional[Union[float, datetime]]" = None) -> None
pass

if isinstance(end_timestamp, datetime):
self._timestamp = end_timestamp
self._end_timestamp = end_timestamp
else:
logger.debug(
"[Tracing] Failed to set end_timestamp. Using current time instead."
)

if self._timestamp is None:
if self._end_timestamp is None:
elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns
self._timestamp = self._start_timestamp + timedelta(
self._end_timestamp = self._start_timestamp + timedelta(
microseconds=elapsed / 1000
)

Expand Down Expand Up @@ -479,8 +479,8 @@ def start_timestamp(self) -> "Optional[datetime]":
return self._start_timestamp

@property
def timestamp(self) -> "Optional[datetime]":
return self._timestamp
def end_timestamp(self) -> "Optional[datetime]":
return self._end_timestamp
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed property breaks openai_agents integration at runtime

High Severity

The timestamp property on StreamedSpan was renamed to end_timestamp, but three call sites in the openai_agents integration still access span.timestamp on spans that can be StreamedSpan instances (when span streaming is enabled via get_start_span_function). This causes an AttributeError at runtime. The most critical site is in _run_single_turn (agent_run.py), where the AttributeError is not wrapped in capture_internal_exceptions(), so it replaces the original exception being handled.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ea2436c. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is true, how come our tests didn't catch this? 🙈

Copy link
Copy Markdown
Contributor Author

@sentrivana sentrivana May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, openai agents doesn't support span streaming yet, so that's ok.


def _is_segment(self) -> bool:
return self._segment is self
Expand Down Expand Up @@ -699,7 +699,7 @@ def start_timestamp(self) -> "Optional[datetime]":
return None

@property
def timestamp(self) -> "Optional[datetime]":
def end_timestamp(self) -> "Optional[datetime]":
return None


Expand Down
18 changes: 12 additions & 6 deletions sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,12 @@ def add_query_source(
if not should_add_query_source:
return

end_timestamp = (
datetime.now(timezone.utc) if span.timestamp is None else span.timestamp
)
if isinstance(span, StreamedSpan):
end_timestamp = span.end_timestamp
else:
end_timestamp = span.timestamp

end_timestamp = end_timestamp or datetime.now(timezone.utc)

duration = end_timestamp - span.start_timestamp
threshold = client.options.get("db_query_source_threshold_ms", 0)
Expand Down Expand Up @@ -442,9 +445,12 @@ def add_http_request_source(
if not should_add_request_source:
return

end_timestamp = (
datetime.now(timezone.utc) if span.timestamp is None else span.timestamp
)
if isinstance(span, StreamedSpan):
end_timestamp = span.end_timestamp
else:
end_timestamp = span.timestamp

end_timestamp = end_timestamp or datetime.now(timezone.utc)

duration = end_timestamp - span.start_timestamp
threshold = client.options.get("http_request_source_threshold_ms", 0)
Expand Down
10 changes: 7 additions & 3 deletions tests/integrations/sqlalchemy/test_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,9 @@ def __init__(self, *args, **kwargs):

if span_streaming:
self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0)
self.span._timestamp = datetime(2024, 1, 1, microsecond=99999)
self.span._end_timestamp = datetime(
2024, 1, 1, microsecond=99999
)
else:
self.span.start_timestamp = datetime(2024, 1, 1, microsecond=0)
self.span.timestamp = datetime(2024, 1, 1, microsecond=99999)
Expand Down Expand Up @@ -1003,7 +1005,9 @@ def __init__(self, *args, **kwargs):

if span_streaming:
self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0)
self.span._timestamp = datetime(2024, 1, 1, microsecond=99999)
self.span._end_timestamp = datetime(
2024, 1, 1, microsecond=99999
)
else:
self.span.start_timestamp = datetime(2024, 1, 1, microsecond=0)
self.span.timestamp = datetime(2024, 1, 1, microsecond=99999)
Expand Down Expand Up @@ -1082,7 +1086,7 @@ def __init__(self, *args, **kwargs):
self.span = span

self.span._start_timestamp = datetime(2024, 1, 1, microsecond=0)
self.span._timestamp = datetime(2024, 1, 1, microsecond=101000)
self.span._end_timestamp = datetime(2024, 1, 1, microsecond=101000)

def __enter__(self):
return self.span
Expand Down
8 changes: 4 additions & 4 deletions tests/integrations/stdlib/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,9 +873,9 @@ def test_no_request_source_if_duration_too_short(
def add_http_request_source_with_pinned_timestamps(span):
if span_streaming:
span._start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0)
span._timestamp = datetime.datetime(2024, 1, 1, microsecond=99999)
span._end_timestamp = datetime.datetime(2024, 1, 1, microsecond=99999)
result = add_http_request_source(span)
span._timestamp = None
span._end_timestamp = None
return result
else:
span.start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0)
Expand Down Expand Up @@ -947,9 +947,9 @@ def test_request_source_if_duration_over_threshold(
def add_http_request_source_with_pinned_timestamps(span):
if span_streaming:
span._start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0)
span._timestamp = datetime.datetime(2024, 1, 1, microsecond=100001)
span._end_timestamp = datetime.datetime(2024, 1, 1, microsecond=100001)
result = add_http_request_source(span)
span._timestamp = None
span._end_timestamp = None
return result
else:
span.start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0)
Expand Down
Loading