diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index fea97564b..9a442e841 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -1887,7 +1887,11 @@ def _outbound_schedule_activity( "Activity must have start_to_close_timeout or schedule_to_close_timeout" ) - handle: _ActivityHandle + # Pre-bind handle to None so the closure cell is not empty when + # Python 3.14's asyncio.Task inspects it eagerly during task naming. + # The coroutine only executes after the real _ActivityHandle is assigned + # below, so handle is always the correct object by the time it is used. + handle = cast(_ActivityHandle, None) # Function that runs in the handle async def run_activity() -> Any: @@ -1977,7 +1981,9 @@ async def _outbound_signal_external_workflow( async def _outbound_start_child_workflow( self, input: StartChildWorkflowInput ) -> _ChildWorkflowHandle: - handle: _ChildWorkflowHandle + # Pre-bind handle to None so the closure cell is not empty when + # Python 3.14's asyncio.Task inspects it eagerly during task naming. + handle = cast(_ChildWorkflowHandle, None) # Common code for handling cancel for start and run def apply_child_cancel_error() -> None: @@ -2042,7 +2048,9 @@ async def _outbound_start_nexus_operation( # resolved with an operation token). See comments in tests/worker/test_nexus.py for worked # examples of the evolution of the resulting handle state machine in the sync and async # Nexus response cases. - handle: _NexusOperationHandle[OutputT] + # Pre-bind handle to None so the closure cell is not empty when + # Python 3.14's asyncio.Task inspects it eagerly during task naming. + handle = cast(_NexusOperationHandle[OutputT], None) async def operation_handle_fn() -> OutputT: while True: