From 840b9663c6d25e5dd477551f3efd49a327c0b471 Mon Sep 17 00:00:00 2001 From: Alex Mazzeo Date: Thu, 11 Dec 2025 12:32:56 -0800 Subject: [PATCH 1/2] Add debugpy bundle import passthrough when debug mode is enabled. debugpy is used by the Microsoft authored VSCode python debugger --- temporalio/worker/_workflow.py | 24 +++++++++++--- tests/worker/test_worker.py | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/temporalio/worker/_workflow.py b/temporalio/worker/_workflow.py index bb489329a..9e2ac9c7b 100644 --- a/temporalio/worker/_workflow.py +++ b/temporalio/worker/_workflow.py @@ -29,6 +29,7 @@ from temporalio.api.enums.v1 import WorkflowTaskFailedCause from temporalio.bridge.worker import PollShutdownError from temporalio.converter import StorageDriverStoreContext, StorageDriverWorkflowInfo +from temporalio.worker.workflow_sandbox._runner import SandboxedWorkflowRunner from . import _command_aware_visitor from ._interceptor import ( @@ -85,6 +86,9 @@ def __init__( encode_headers: bool, max_workflow_task_external_storage_concurrency: int, ) -> None: + # Debug mode is enabled if specified or if the TEMPORAL_DEBUG env var is truthy + debug_mode = debug_mode or bool(os.environ.get("TEMPORAL_DEBUG")) + self._bridge_worker = bridge_worker self._namespace = namespace self._task_queue = task_queue @@ -96,7 +100,19 @@ def __init__( ) ) self._workflow_task_executor_user_provided = workflow_task_executor is not None + + # If debug mode is enabled, ensure that the debugpy (https://github.com/microsoft/debugpy) + # import is added as a passthrough + if debug_mode and isinstance(workflow_runner, SandboxedWorkflowRunner): + workflow_runner = dataclasses.replace( + workflow_runner, + restrictions=workflow_runner.restrictions.with_passthrough_modules( + "_pydevd_bundle" + ), + ) + self._workflow_runner = workflow_runner + self._unsandboxed_workflow_runner = unsandboxed_workflow_runner self._data_converter = data_converter # Build the interceptor classes and collect extern functions @@ -127,11 +143,9 @@ def __init__( ) self._throw_after_activation: Exception | None = None - # If there's a debug mode or a truthy TEMPORAL_DEBUG env var, disable - # deadlock detection, otherwise set to 2 seconds - self._deadlock_timeout_seconds = ( - None if debug_mode or os.environ.get("TEMPORAL_DEBUG") else 2 - ) + # If debug mode is enabled, disable deadlock detection + # otherwise set to 2 seconds + self._deadlock_timeout_seconds = None if debug_mode else 2 # Keep track of workflows that could not be evicted self._could_not_evict_count = 0 diff --git a/tests/worker/test_worker.py b/tests/worker/test_worker.py index bd9d9b898..dda754a5b 100644 --- a/tests/worker/test_worker.py +++ b/tests/worker/test_worker.py @@ -4,8 +4,10 @@ import concurrent.futures import multiprocessing import multiprocessing.context +import os import uuid from collections.abc import Awaitable, Callable, Sequence +from contextlib import contextmanager from datetime import timedelta from typing import Any from urllib.request import urlopen @@ -57,6 +59,7 @@ WorkerTuner, WorkflowSlotInfo, ) +from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner from temporalio.workflow import DynamicWorkflowConfig, VersioningIntent from tests.helpers import ( assert_eventually, @@ -1650,3 +1653,57 @@ def test_worker_config_matches_init_params(): f"Missing from config: {init_params - config_keys}. " f"Extra in config: {config_keys - init_params}." ) + + +async def test_worker_debug_mode(client: Client): + worker = Worker( + client, + workflows=[SimpleWorkflow], + task_queue=f"task-queue-{uuid.uuid4()}", + ) + assert worker._workflow_worker + assert worker._workflow_worker._deadlock_timeout_seconds == 2 + assert isinstance(worker._workflow_worker._workflow_runner, SandboxedWorkflowRunner) + assert ( + "_pydevd_bundle" + not in worker._workflow_worker._workflow_runner.restrictions.passthrough_modules + ) + + worker = Worker( + client, + workflows=[SimpleWorkflow], + task_queue=f"task-queue-{uuid.uuid4()}", + debug_mode=True, + ) + assert worker._workflow_worker + assert worker._workflow_worker._deadlock_timeout_seconds is None + assert isinstance(worker._workflow_worker._workflow_runner, SandboxedWorkflowRunner) + assert ( + "_pydevd_bundle" + in worker._workflow_worker._workflow_runner.restrictions.passthrough_modules + ) + + @contextmanager + def debug_envvar(): + os.environ["TEMPORAL_DEBUG"] = "true" + try: + yield + finally: + os.environ.pop("TEMPORAL_DEBUG") + + with debug_envvar(): + worker = Worker( + client, + workflows=[SimpleWorkflow], + task_queue=f"task-queue-{uuid.uuid4()}", + ) + assert worker._workflow_worker + assert worker._workflow_worker._deadlock_timeout_seconds is None + assert isinstance( + worker._workflow_worker._workflow_runner, + SandboxedWorkflowRunner, + ) + assert ( + "_pydevd_bundle" + in worker._workflow_worker._workflow_runner.restrictions.passthrough_modules + ) From 4c4d4f6f5fa7879eaa04ba05d6d0b9365389998a Mon Sep 17 00:00:00 2001 From: Elizabeth Locke Date: Thu, 21 May 2026 11:35:53 -0400 Subject: [PATCH 2/2] Document debug_mode for IDE debuggers in workflow sandbox Adds a short subsection to the Workflow Sandbox documentation pointing users at debug_mode=True (or the TEMPORAL_DEBUG env var) for attaching IDE debuggers to sandboxed workflow code. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d284fad59..a9adf8067 100644 --- a/README.md +++ b/README.md @@ -1241,6 +1241,14 @@ my_worker = Worker(..., workflow_runner=SandboxedWorkflowRunner(restrictions=my_ See the API for more details on exact fields and their meaning. +##### Debugging Workflows in the Sandbox + +To attach an IDE debugger (e.g. the VSCode Python debugger or PyCharm) to workflow code running inside the +sandbox, set `debug_mode=True` on the `Worker`, or set the `TEMPORAL_DEBUG` environment variable to a +truthy value. This disables the workflow deadlock detector — which would otherwise interrupt a paused +workflow at the 2-second timeout — and adds `_pydevd_bundle` (the module IDE debuggers inject at runtime) +to the sandbox passthrough list so breakpoints in workflow code are hit. + ##### Known Sandbox Issues Below are known sandbox issues. As the sandbox is developed and matures, some may be resolved.