Skip to content

Next Python SDK major - #5005

Draft
sentrivana wants to merge 256 commits into
masterfrom
major/3.0
Draft

sentrivana wants to merge 256 commits into
masterfrom
major/3.0

Conversation

@sentrivana

@sentrivana sentrivana commented Oct 24, 2025

Copy link
Copy Markdown
Contributor

We're preparing our next major on this branch.

The project is tracked in Linear. If you don't have access, we'll try to tag issues belonging to the project with the SDK 3.0 label on GitHub so that you can follow along.

Notable changes

  • Transaction-based tracing will be removed. Span streaming will be the default tracing model.
  • Python 3.6 support will be removed.

Context

You might have read this announcement about us discontinuing work on a 3.0. This is referring to the work done on the potel-base branch, which included two types of changes: a huge refactor of our tracing code on the one hand, and various unrelated changes, improvements and fixes on the other. We're dropping the huge refactor part, and only porting the rest, to a new branch and eventually a new 3.0 release.

@codecov

codecov Bot commented Oct 24, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.76%. Comparing base (14aff96) to head (d864ba0).
⚠️ Report is 4 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #5005       +/-   ##
===========================================
+ Coverage   70.55%   83.76%   +13.21%     
===========================================
  Files         180      180               
  Lines       18077    18080        +3     
  Branches     3008     3009        +1     
===========================================
+ Hits        12754    15145     +2391     
+ Misses       4432     1943     -2489     
- Partials      891      992      +101     
Files with missing lines Coverage Δ
sentry_sdk/integrations/__init__.py 88.42% <100.00%> (+0.37%) ⬆️

... and 61 files with indirect coverage changes

@github-actions

github-actions Bot commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊

66913 passed | ❌ 1 failed | ⏭️ 3019 skipped | Total: 69933 | Pass Rate: 95.68% | Execution Time: 189m 32s

📊 Comparison with Base Branch

Metric Change
Total Tests 📉 -66968
Passed Tests 📉 -62840
Failed Tests 📈 +1
Skipped Tests 📉 -4129

➕ New Tests (1)

View new tests
  • test_input_attributes_nonstreaming_chat[True-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
    • Status: ❌ Failing

❌ Failed Tests

test_input_attributes_nonstreaming_chat[True-messages0-expected_system_instructions0-expected_input_messages0]

File: tests.integrations.mistral.test_mistral
Suite: py3.10-mistral-v2.0.5
Error: KeyError: 140715163947008

Stack Trace
tests/integrations/mistral/test_mistral.py:380: in test_input_attributes_nonstreaming_chat
    sentry_init(
tests/conftest.py:349: in inner
    client = sentry_sdk.Client(*a, **kw)
sentry_sdk/client.py:278: in __init__
    self._init_impl()
sentry_sdk/client.py:428: in _init_impl
    self.integrations = setup_integrations(
sentry_sdk/integrations/__init__.py:216: in setup_integrations
    for integration_cls in iter_default_integrations(
sentry_sdk/integrations/__init__.py:44: in iter_default_integrations
    yield getattr(import_module(module), cls)
/usr/lib/python3.10/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
<frozen importlib._bootstrap>:1050: in _gcd_import
    ???
<frozen importlib._bootstrap>:1024: in _find_and_load
    ???
<frozen importlib._bootstrap>:171: in __enter__
    ???
<frozen importlib._bootstrap>:123: in acquire
    ???
E   KeyError: 140715163947008

✅ Patch coverage is 90.13%. Project has 2088 uncovered lines.
❌ Project coverage is 90.13%. Comparing base (c3a7b52) to head (35f70d2).

Coverage diff
@@            Coverage Diff             @@
##        master       #PR       +/-##
==========================================
- Coverage    90.20%    90.13%    -0.07%
==========================================
  Files          194       180       -14
  Lines        25997     21155     -4842
  Branches      9662      7656     -2006
==========================================
+ Hits         23448     19067     -4381
- Misses        2549      2088      -461
- Partials      1450      1199      -251

Generated by Codecov Action

@github-actions

github-actions Bot commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (logging) Separate ignore lists for events/breadcrumbs and sentry logs by sl0thentr0py in #5698

Bug Fixes 🐛

Anthropic

  • Set exception info on streaming span when applicable by alexander-alderman-webb in #5683
  • Patch AsyncStream.close() and AsyncMessageStream.close() to finish spans by alexander-alderman-webb in #5675
  • Patch Stream.close() and MessageStream.close() to finish spans by alexander-alderman-webb in #5674

Documentation 📚

  • Add note on AI PRs to CONTRIBUTING.md by sentrivana in #5696

Internal Changes 🔧

  • Add -latest alias for each integration test suite by sentrivana in #5706
  • Use date-based branch names for toxgen PRs by sentrivana in #5704
  • 🤖 Update test matrix with new releases (03/19) by github-actions in #5703
  • Add client report tests for span streaming by sentrivana in #5677

Other

  • Next Python SDK major by sentrivana in #5005
  • Update CHANGELOG.md by sentrivana in #5685

🤖 This preview updates automatically when you update the PR.

Comment thread sentry_sdk/consts.py
Remove everything hub related, including all sorts of compatibility
shims around hubs/scopes.

Also remove deprecated session methods.

`configure_scope` and `push_scope` removal coming in a future PR.

#### Issues
Closes #5001
The integration requires additional configuration which should be
intentional on the user's part.

#### Issues
Closes #4993
- Remove everything in `integrations/opentelemetry`
(`SentrySpanProcessor`, `SentryPropagator`, etc.)
- Remove associated test files and CI config
- Move old propagator functions and consts that we were using in
`OTLPIntegration` to the OTLP propagator directly
- Remove `instrumenter`

Note: `NoOpSpan` was not removed because it makes mypy blow up. Not
worth the effort as we'll anyway get rid of it when dropping transaction
based tracing.

#### Issues
Closes #6932
Comment thread sentry_sdk/integrations/otlp.py
### Description
The API is deprecated and slated for removal in 3.0.

#### Issues
Closes #5019

#### Reminders
- Please add tests to validate your changes, and lint your code using
`uv run ruff`.
- Add GH Issue ID _&_ Linear ID (if applicable)
- PR title should use [conventional
commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type)
style (`feat:`, `fix:`, `ref:`, `meta:`)
- For external contributors:
[CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md),
[Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord
community](https://discord.gg/Ww9hbqr)
### Description
Remove the deprecated API.

#### Issues
Closes #5018
…new major (#6948)

### Description
When `failed_request_status_codes` was first introduced, it accepted a
different format. The format was then changed, while the old format was
deprecated. Drop support for the old format now.

#### Issues
Closes #5017
### Description
Most of the entries in our extras list serve as a way to
communicate/enforce the lower boundary of the respective framework that
we support. This creates a parallel system to the version checks we
already have in each integration.

Some extras, however, define extra dependencies or specific extras that
are required for an integration to work correctly (e.g. the Flask
integration needs `blinker` to work properly). In that case, keep the
extra.

#### Issues
Closes #6259
Comment thread tests/integrations/django/asgi/test_asgi.py
Comment thread sentry_sdk/integrations/otlp.py
Comment thread tests/integrations/threading/test_threading.py Outdated
sentrivana and others added 19 commits September 14, 2026 11:12
- Remove the `Span` and `Transaction` classes and associated types
- Remove transaction/old span support from transport, client, scope
- Rewire top-level API to `sentry_sdk.traces.*`
- Remove old span counting/reporting (`dropped_spans` etc.)
- Remove `has_span_streaming_enabled`
- ...and more

It's a big diff but no logic changes, just removing anything old span
related.

Closes
https://linear.app/getsentry/issue/PY-2654/rewire-top-level-tracing-api-to-span-streaming
Closes https://linear.app/getsentry/issue/PY-2655/remove-legacy-tracing
Closes
https://linear.app/getsentry/issue/PY-2653/remove-has-span-streaming-enabled-branches-from-core
This original message count is never set since truncation for GenAI spans was removed.
Rely only on cached properties on the `Request` instance to attach the request body to events.

Closes #7285
Closes #4827
Closes #4764
Remove last mention of `set_data` (it's commented out, but still). 

Closes https://linear.app/getsentry/issue/PY-1946/deprecatedrop-set-data
… present (#7493)

Use a boolean scope member to suppress chat generation spans from client libraries that are inside a chat generation span from an agent framework.
Add the `traces._AgentFrameworkChatGenerationContext` wrapper to ensure the scope member is reset when the agent framework span exits.

Remove logic that disables client library integrations when an agent library is active.
Delete the `integration_deactivation` tox environment that ran the corresponding tests.

Use the context manager in all agent frameworks that create chat generation spans (`langchain`, `pydantic-ai`, and `openai-agents`).
Add early returns for chat generation spans in the `openai`, `anthropic`, and `google-genai` client libraries to prevent duplicate generation spans.

Remove embedding patches from the `langchain` integration. These must now be provided by client libraries like `openai`, `anthropic`, etc.

Closes #5515
- remove `scope.transaction` (`set_transaction_name` remains for now;
changed all direct uses of `scope.transaction =` to use
`set_transaction_name` instead)
- remove more interop utils
(`_serialized_v1_attribute_to_serialized_v2_attribute` etc.)
- remove event processor-based transaction name setting (superseded by
`set_transaction_name` directly in patches)
- ...
- `Scope.streamed_span` -> `Scope.span`
- `StreamedSpan` -> `Span` (alias still kept for backwards compat)
- `NoOpStreamedSpan` -> `NoOpSpan` (alias still kept for backwards
compat)
- removed transaction tracing code from a test helper

The three hanging Web tests will be fixed in
#7511

Closes #7469
…spans (#7512)

Remove the response model from Invoke Agent spans because it is ambiguous for an agent that can call different models in the course of its execution.
…t spans (#7513)

Remove the response model from Invoke Agent spans because it is ambiguous for an agent that can call different models in the course of its execution.
Remove token attributes from Invoke Agent spans because it is ambiguous whether they include usage from tool calls.
Remove token attributes from Invoke Agent spans because it is ambiguous whether they include usage from tool calls.
Comment on lines 714 to +751
@@ -756,22 +735,21 @@ async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any":
@wraps(tool)
def sync_wrapped(*args: "Any", **kwargs: "Any") -> "Any":
with _create_tool_span(tool_name, tool_doc) as span:
set_on_span = (
span.set_attribute
if isinstance(span, StreamedSpan)
else span.set_data
)
# Capture tool input
tool_input = _capture_tool_input(args, kwargs, tool)
with capture_internal_exceptions():
set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_input))
span.set_attribute(
SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_input)
)

try:
result = tool(*args, **kwargs)

# Capture tool output
with capture_internal_exceptions():
set_on_span(SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result))
span.set_attribute(
SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result)

@sentry-warden sentry-warden Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tool input/output always sent without PII/data_collection guards

wrapped_tool always serializes full tool args and return values onto spans, bypassing the gen_ai inputs/outputs and include_prompts checks used elsewhere in this integration. Gate GEN_AI_TOOL_INPUT/OUTPUT the same way prompts and response text are gated.

Evidence
  • In async_wrapped and sync_wrapped, _capture_tool_input(args, kwargs, tool) and the tool result are passed to span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT/OUTPUT, safe_serialize(...)) with no privacy check.
  • The same file gates prompts, response text, and related gen_ai content behind has_data_collection_enabled(...gen_ai.inputs/outputs) or should_send_default_pii() and integration.include_prompts (e.g. set_span_data_for_request / set_span_data_for_response).
  • Sibling AI integrations (LangChain, Pydantic AI, OpenAI Agents) only set tool input/output when those same guards allow it.
  • Tool args/results are application-controlled and commonly contain user content, secrets, or PII, so they are sent to Sentry even when send_default_pii=False or data_collection.gen_ai.*=False.

Identified by Warden · security-review, find-bugs, code-review · 59T-VTZ

Comment thread MIGRATION_GUIDE.md
Comment on lines +8 to +31

## Changed

- The Strawberry integration won't auto-enable anymore if we detect `strawberry-graphql` is installed. Set it up manually, setting the `async_execution` integration option to either `True` or `False` depending on if your app is async or sync.

```python
from sentry_sdk.integrations.strawberry import StrawberryIntegration

sentry_sdk.init(
integrations=[
StrawberryIntegration(async_execution=True), # or False
],
...
)
```

- The FastAPI and Starlette integrations no longer eagerly consume the request body. As a result, the body is only reported in events if your handler parsed it with `Request.json()` or `Request.form()` before the event is captured.
- The UnraisableHookIntegration is now enabled by default.
- We now don't suppress chained exceptions in the ASGI and asyncio integrations by default. The related `suppress_asgi_chained_exceptions` experimental option was removed.
- In the AWS Lambda and GCP integrations, the message of the warning the SDK optionally emits if a function is about to time out has changed.
- We changed the way we emit warnings. Deprecations will from now on be always emitted using `warnings.warn()`, while all other warnings will be emitted using `logger.warning()`.
- `sentry_sdk.init()` can no longer be used as a context manager.
- The `@trace` decorator doesn't accept a `template` parameter anymore.
- The option `attach_stacktrace` is now `True` by default, meaning the SDK will attach stack traces to messages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Migration guide omits transaction-to-span-streaming API break

Document removal of start_transaction/Transaction and the start_span signature change (op/descriptionname/attributes), with concrete before/after examples—this is the headline 3.0 break and currently has no migration path here.

Evidence
  • PR states transaction-based tracing is removed and span streaming is the default tracing model.
  • sentry_sdk/api.py exports start_span, continue_trace, and new_trace only—no start_transaction.
  • sentry_sdk/traces.py start_span(name=..., attributes=...) replaces the old op/description API; tracing.py/Transaction are gone.
  • MIGRATION_GUIDE.md Changed/Removed sections (lines 1–119+) never mention start_transaction, Transaction, or how to rewrite existing tracing calls.
Also found at 1 additional location
  • sentry_sdk/__init__.py:48-49

Identified by Warden · code-review · YNX-E65

Comment on lines +142 to +143
if sentry_sdk.get_current_scope()._agent_framework_chat_generation_entered:
return f(self, *args, **kwargs)

@sentry-warden sentry-warden Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Async stream early-return drops await and returns a coroutine

When _agent_framework_chat_generation_entered is set, new_async_generate_content_stream returns f(...) instead of await f(...), so callers get a coroutine instead of the stream.

Evidence
  • The surrounding path at line 140 correctly does return await f(self, *args, **kwargs) when the integration is missing.
  • The new early-return at lines 142-143 does return f(self, *args, **kwargs) with no await.
  • Sibling async integrations (openai.py, anthropic.py) await on the same _agent_framework_chat_generation_entered guard.
Also found at 1 additional location
  • sentry_sdk/integrations/google_genai/__init__.py:245-246

Identified by Warden · code-review, find-bugs · QMM-3HM

Comment on lines 319 to 325

def _exit_span(
self: "SentryLangchainCallback",
span: "Union[sentry_sdk.tracing.Span, StreamedSpan]",
span: "Span",
run_id: "UUID",
) -> None:
span.__exit__(None, None, None)

@sentry-warden sentry-warden Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Chat generation exit skips context cleanup and leaves suppression flag set

_exit_span only ends the inner Span, so successful on_chat_model_end paths never call _AgentFrameworkChatGenerationContext.__exit__() and leave _agent_framework_chat_generation_entered stuck true, suppressing later OpenAI/Anthropic/Google GenAI spans on that scope. Exit the stored context (as _handle_error already does) instead of unwrapping to the raw span.

Evidence
  • _create_generation_span() stores an _AgentFrameworkChatGenerationContext in span_map; that class sets span._scope._agent_framework_chat_generation_entered = True in __init__ and only clears it in __exit__.
  • _exit_span() was changed to accept only a Span and call span.__exit__(), which ends the span but does not clear the flag.
  • on_chat_model_end unwraps context.span and calls _exit_span(span, run_id), so the success path never invokes context.__exit__().
  • _handle_error() still calls context.__exit__() correctly, so only successful chat completions leak the flag.
  • OpenAI/Anthropic/Google GenAI integrations skip instrumentation when get_current_scope()._agent_framework_chat_generation_entered is true.
Also found at 2 additional locations
  • sentry_sdk/integrations/langchain.py:537-561
  • sentry_sdk/integrations/langchain.py:18-18

Identified by Warden · code-review, find-bugs · 7R5-TEG

Comment thread docs/apidocs.rst
Comment thread sentry_sdk/consts.py
Comment thread sentry_sdk/consts.py
Comment on lines 1287 to 1293
send_client_reports: bool = True,
_experiments: "Experiments" = {}, # noqa: B006
proxy_headers: "Optional[Dict[str, str]]" = None,
instrumenter: "Optional[str]" = INSTRUMENTER.SENTRY,
before_send_transaction: "Optional[TransactionProcessor]" = None,
project_root: "Optional[str]" = None,
enable_tracing: "Optional[bool]" = None,
include_local_variables: "Optional[bool]" = True,
include_source_context: "Optional[bool]" = True,
trace_propagation_targets: "Optional[Sequence[str]]" = [ # noqa: B006

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed before_send_transaction option is undocumented and has no equivalent migration guidance

Existing applications that pass before_send_transaction to sentry_sdk.init() now fail during initialization with TypeError: Unknown option 'before_send_transaction'. The 3.0 migration guide does not document this removal or explain that transaction-level filtering is no longer available; before_send_span is not a direct equivalent because streamed spans cannot be dropped.

Evidence
  • ClientConstructor.__init__ no longer declares before_send_transaction, so it is absent from DEFAULT_OPTIONS generated by _get_default_options().
  • _get_options() rejects the legacy keyword with TypeError("Unknown option %r" % (key,)) before the client can initialize.
  • The migration guide lists other removed options but does not mention before_send_transaction or the loss of transaction-level filtering.
  • Client._capture_telemetry() routes the replacement callback through before_send_span, whose implementation explicitly keeps spans even when the callback returns None.

Identified by Warden · code-review · YR8-HD6

Comment thread sentry_sdk/integrations/bottle.py
Comment on lines 280 to 285
if new_messages is None:
return

_set_usage_data(span, new_messages)
_set_response_model_name(span, new_messages)

if _should_record_outputs(integration):
llm_response_text = _extract_llm_response_text(new_messages)
if llm_response_text:

@sentry-warden sentry-warden Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Langgraph agent spans lose usage and response model attributes

Removing _set_usage_data and _set_response_model_name drops gen_ai.usage.* and gen_ai.response.model from invoke_agent spans; if this was only StreamedSpan cleanup, keep setting them via set_attribute/set_data_normalized.

Evidence
  • The hunk deletes _set_usage_data and _set_response_model_name, and removes their calls from _set_response_attributes.
  • Those helpers previously aggregated response_metadata.token_usage and model_name onto the langgraph invoke_agent span.
  • _set_response_attributes still only records response text/tool calls, so usage and model attributes are no longer set on this path.
  • Sibling AI integrations (e.g. langchain callbacks) still record usage/model on their own spans, so this is a langgraph-agent-span behavior change, not a global AI attribute removal.

Identified by Warden · code-review, find-bugs · GAY-67G

Comment thread sentry_sdk/integrations/openai_agents/__init__.py
Comment on lines 219 to +222
include_pii=_should_record(integration, "outputs"),
)
yield x
_end_span(span)
span.end()

@sentry-warden sentry-warden Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Streaming iterator swallows user exceptions via capture_internal_exceptions

Wrap the stream loop so capture_internal_exceptions only guards SDK collection, not yield; otherwise stream errors are swallowed and the caller never sees them.

Evidence
  • new_iterator() wraps for x in old_iterator: ... yield x in with capture_internal_exceptions().
  • capture_internal_exceptions().__exit__ returns True, so any exception from the Cohere stream or from the generator is suppressed.
  • span.end() still runs after the suppressed exception, so the caller gets a silent truncated stream and an OK span.
  • Sibling integrations (google_genai, langchain) re-raise stream exceptions and only use capture_internal_exceptions around telemetry.
Also found at 1 additional location
  • sentry_sdk/integrations/cohere.py:228-234

Identified by Warden · find-bugs, code-review · HCZ-A5T

Comment thread sentry_sdk/client.py
Comment thread sentry_sdk/integrations/aiohttp.py
Comment on lines 84 to +92
# This logger logs every status of every task that ran on the worker.
# Meaning that every task's breadcrumbs are full of stuff like "Task
# <foo> raised unexpected <bar>".
ignore_logger("celery.worker.job")
ignore_logger("celery.app.trace")
ignore_logger_for_events("celery.worker.job")
ignore_logger_for_events("celery.app.trace")

# This is stdout/err redirected to a logger, can't deal with this
# (need event_level=logging.WARN to reproduce)
ignore_logger("celery.redirected")
ignore_logger_for_events("celery.redirected")

@sentry-warden sentry-warden Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Celery control-flow exceptions not registered, so task segment still ends as error

setup_once() never calls _register_control_flow_exception() for Celery Retry/Ignore/Reject, so the outer task segment still becomes error on span exit even when the exception is treated as non-error control flow.

Evidence
  • Celery setup_once() only patches tracers/workers and ignores loggers; it does not register CELERY_CONTROL_FLOW_EXCEPTIONS.
  • Span.__exit__ sets SpanStatus.ERROR whenever should_be_treated_as_error(ty, value) is true.
  • should_be_treated_as_error() only skips types previously passed to _register_control_flow_exception().
  • Huey and ARQ both register their control-flow exceptions in setup_once(); Celery does not, so uncaught Retry/Ignore/Reject still fail the outer queue.task.celery segment.
Also found at 1 additional location
  • sentry_sdk/integrations/celery/__init__.py:95-100

Identified by Warden · find-bugs, code-review · GQL-YD4

Comment thread sentry_sdk/integrations/cohere.py
Comment thread sentry_sdk/integrations/grpc/aio/server.py
Comment thread sentry_sdk/integrations/grpc/server.py
ericapisani and others added 3 commits September 16, 2026 09:48
Change `cache_spans` to default to `True` and remove the Spotlight/DEBUG
override, so the option is always respected as given.

Fixes PY-154
Fixes #3300
Bottle seems to be the only web framework integration that sets the
transaction name late, so errors that happen before that might not get
associated to the correct segment name. We can't move the logic because
we don't have enough information before that point. Restore the
transaction setting logic in the event processor.

Closes
https://linear.app/getsentry/issue/PY-2784/double-check-that-set-transaction-name-is-set-early-enough
Comment thread sentry_sdk/api.py
Comment thread sentry_sdk/integrations/django/__init__.py
Comment on lines +1182 to 1208
span = sentry_sdk.traces.start_span(
name=f"invoke_agent {run_name}" if run_name else "invoke_agent",
attributes={
"sentry.op": OP.GEN_AI_INVOKE_AGENT,
"sentry.origin": LangchainIntegration.origin,
SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent",
SPANDATA.GEN_AI_RESPONSE_STREAMING: True,
},
)

if run_name:
span.set_data(SPANDATA.GEN_AI_FUNCTION_ID, run_name)
if run_name:
span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name)

_set_tools_on_span(span, tools)

input = args[0].get("input") if len(args) >= 1 else None
if input is not None and record_inputs:
normalized_messages = normalize_message_roles([input])

scope = sentry_sdk.get_current_scope()
messages_data = (
truncate_and_annotate_messages(normalized_messages, span, scope)
if should_truncate_gen_ai_input(client.options)
else normalized_messages
set_data_normalized(
span,
SPANDATA.GEN_AI_REQUEST_MESSAGES,
normalized_messages,
unpack=False,
)
if messages_data is not None:
set_data_normalized(
span,
SPANDATA.GEN_AI_REQUEST_MESSAGES,
messages_data,
unpack=False,
)

# Run the agent
result = f(self, *args, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AgentExecutor.stream leaves invoke_agent span active if setup raises

Wrap result = f(self, *args, **kwargs) in try/except and call span.__exit__(*exc_info) on failure so a setup error does not leave the active span on the scope (see google_genai stream wrappers).

Evidence
  • new_stream() calls sentry_sdk.traces.start_span(...), and Span._start() immediately sets the span as the current scope span when active=True.
  • result = f(self, *args, **kwargs) is unguarded; only new_iterator / new_iterator_async call span.__exit__.
  • If AgentExecutor.stream raises before returning an iterator, the span is never finished and remains the active span for later work.
  • google_genai stream wrappers catch exceptions around the underlying call and finish the span; this path does not.

Identified by Warden · code-review · GUS-JBC

Comment thread sentry_sdk/client.py
Comment on lines 777 to 800
@@ -1169,44 +794,7 @@ def capture_event(

envelope = Envelope(headers=headers)

if is_transaction and isinstance(profile, Profile):
envelope.add_profile(profile.to_json(event_opt, self.options))

if is_transaction and not span_recorder_has_gen_ai_span:
envelope.add_transaction(event_opt)
elif is_transaction:
split_spans = _split_gen_ai_spans(event_opt)
if split_spans is None or not split_spans[1]:
envelope.add_transaction(event_opt)
else:
non_gen_ai_spans, gen_ai_spans = split_spans

event_opt["spans"] = non_gen_ai_spans
envelope.add_transaction(event_opt)

converted_gen_ai_spans = [
_serialized_v1_span_to_serialized_v2_span(span, event_opt)
for span in gen_ai_spans
if isinstance(span, dict)
]

envelope.add_item(
Item(
type=SpanBatcher.TYPE,
content_type=SpanBatcher.CONTENT_TYPE,
headers={
"item_count": len(converted_gen_ai_spans),
},
payload=PayloadRef(
json={
"version": 2,
"items": converted_gen_ai_spans,
},
),
)
)

elif is_checkin:
if is_checkin:
envelope.add_checkin(event_opt)
else:
envelope.add_event(event_opt)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Transaction events now sent as error events

After removing transaction handling, events with type "transaction" still pass through and are sampled as errors then envelope.add_event()'d; drop or reject them explicitly instead of mis-categorizing.

Evidence
  • Previously is_transaction skipped _should_sample_error and used envelope.add_transaction(event_opt).
  • That branch is gone; only is_checkin is special-cased, so type == "transaction" falls into the else path.
  • envelope.add_event() always sets item type to "event", so a transaction payload is emitted as an error event.
  • Call sites still exist that pass {"type": "transaction"} into capture_event (e.g. tests/test_transport.py), and Event typing still allows "transaction".

Identified by Warden · code-review · 3PJ-F9H

Comment thread sentry_sdk/consts.py
Comment on lines 1279 to 1281
ca_certs: "Optional[str]" = None,
propagate_traces: bool = True,
traces_sample_rate: "Optional[float]" = None,
trace_lifecycle: "Optional[Literal['static', 'stream']]" = None,
traces_sampler: "Optional[TracesSampler]" = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

trace_lifecycle removed without migration note

Removing trace_lifecycle will hard-fail existing init(trace_lifecycle=...) callers, and MIGRATION_GUIDE.md does not document the removal or that stream mode is now the only behavior.

Evidence
  • The hunk deletes trace_lifecycle: Optional[Literal['static', 'stream']] from the public constructor signature.
  • DEFAULT_OPTIONS is generated from ClientConstructor.__init__, so the option disappears from accepted config keys.
  • _get_options() rejects unknown keys with TypeError, so old configs break at init time.
  • MIGRATION_GUIDE.md has no entry for trace_lifecycle, while related removals like stream_gen_ai_spans are explicitly listed.

Identified by Warden · code-review · KAM-DSE

Comment on lines +179 to +186
streaming_span = sentry_sdk.traces.start_span(
name=span.name,
parent_span=span,
attributes={
"sentry.op": OP.HTTP_CLIENT_STREAM,
"sentry.origin": Boto3Integration.origin,
},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Boto3 streaming span remains active and hijacks subsequent span parenting

Create the streaming span with active=False (or explicitly scope it only around body reads). The current implementation leaves it as the active span for the entire lifetime of the returned StreamingBody, causing unrelated spans and trace propagation to use the HTTP streaming span as their parent and allowing delayed cleanup to overwrite the current span stack.

Evidence
  • _sentry_after_call() ends the request span at line 173, then starts streaming_span with the default active=True at lines 179-186.
  • Span._start() replaces scope.span with the streaming span, so spans created while the body remains open are parented to http.client.stream rather than the caller's span.
  • streaming_span.end() runs later from read() or close() and restores the span saved at creation; if cleanup occurs while another span is active, _end() unconditionally overwrites that current scope entry, corrupting the span stack.
  • Existing boto3 streaming tests assert emitted span metadata and parent IDs but do not verify the current span or nested spans while the body is open.

Identified by Warden · find-bugs · L6S-EH8

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants