Skip to content
Open
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
5 changes: 3 additions & 2 deletions sentry/interceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)

with workflow.unsafe.imports_passed_through():
import asyncio
import sentry_sdk


Expand Down Expand Up @@ -43,7 +44,7 @@ async def execute_activity(self, input: ExecuteActivityInput) -> Any:
if is_dataclass(arg) and not isinstance(arg, type):
scope.set_context("temporal.activity.input", asdict(arg))
scope.set_context("temporal.activity.info", activity.info().__dict__)
scope.capture_exception()
await asyncio.to_thread(scope.capture_exception, e)
raise e


Expand Down Expand Up @@ -71,7 +72,7 @@ async def execute_workflow(self, input: ExecuteWorkflowInput) -> Any:
scope.set_context("temporal.workflow.info", workflow.info().__dict__)
if not workflow.unsafe.is_replaying():
with workflow.unsafe.sandbox_unrestricted():
scope.capture_exception()
await asyncio.to_thread(scope.capture_exception, e)
Copy link
Member

@cretz cretz Dec 19, 2025

Choose a reason for hiding this comment

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

This is not legal code from a workflow, and as you saw, is not supported by our event loop (by intention). Two things here...

First, Sentry is on the verge of a 3.x SDK which will include async support (per getsentry/sentry-python#2824 (comment)). They appear to have released a few alphas already.

Second, not sure we should assume they want to use the default executor and you're going to have to use an intentionally-undocumented feature called "unsafe extern" here (we use this for a similar purpose for OTel at https://github.com/temporalio/sdk-python/blob/main/temporalio/contrib/opentelemetry.py). I would suggest doing the following:

  • Have the constructor of SentryInterceptor accept an optional ThreadPoolExecutor, and capture the current event loop as a private attribute on the class
  • Have a def _capture_exception(self, scope: sentry_sdk.Scope, exception: Exception) that calls the loop's run_in_executor with the known thread pool executor for scope's capture_exception
  • Pass the SentryInterceptor to _SentryActivityInboundInterceptor's interceptor so it can use this
  • On workflow_interceptor_class, call input.unsafe_extern_functions.update("__temporal_sentry_capture_exception", self._capture_exception)
  • In _SentryWorkflowInterceptor instead of what's called today, call temporalio.workflow.extern_functions()["__temporal_sentry_capture_exception"](scope, e)

But would understand if there isn't value in doing all of this if it's just going to be outdated shortly with v3 SDK

raise e


Expand Down