Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
18 changes: 9 additions & 9 deletions sentry_sdk/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@
self._sample_rate = sample_rate

self._start_timestamp = datetime.now(timezone.utc)
self._timestamp: "Optional[datetime]" = None
self._end_timestamp: "Optional[datetime]" = None

Check failure on line 294 in sentry_sdk/traces.py

View check run for this annotation

@sentry/warden / warden: find-bugs

__slots__ still declares `_timestamp`, breaking every StreamedSpan instantiation after rename

The rename of `_timestamp` to `_end_timestamp` was not propagated to `StreamedSpan.__slots__` (sentry_sdk/traces.py line 247). Because the class defines `__slots__`, assigning `self._end_timestamp = None` in `__init__` (line 294) raises `AttributeError: 'StreamedSpan' object has no attribute '_end_timestamp'`. Every attempt to create a StreamedSpan will fail at construction time, breaking all span streaming functionality.
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 __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 @@
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 @@
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 @@
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 @@
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
Loading