From 52ed7137afefb087429b7ea6691af46f01b94996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Fri, 27 Mar 2026 19:03:23 -0700 Subject: [PATCH 1/2] Yield after create_task to ensure timer tasks are scheduled Add `await asyncio.sleep(0)` after `create_task()` calls in UserIdleController, SpeechTimeoutUserTurnStopStrategy, TurnAnalyzerUserTurnStopStrategy, and UserTurnCompletionLLMServiceMixin so the event loop schedules the newly created timer tasks before the caller continues. --- src/pipecat/turns/user_idle_controller.py | 2 ++ .../turns/user_stop/speech_timeout_user_turn_stop_strategy.py | 4 ++++ .../turns/user_stop/turn_analyzer_user_turn_stop_strategy.py | 4 ++++ src/pipecat/turns/user_turn_completion_mixin.py | 2 ++ 4 files changed, 12 insertions(+) diff --git a/src/pipecat/turns/user_idle_controller.py b/src/pipecat/turns/user_idle_controller.py index b3b7e8074..188daa327 100644 --- a/src/pipecat/turns/user_idle_controller.py +++ b/src/pipecat/turns/user_idle_controller.py @@ -143,6 +143,8 @@ class UserIdleController(BaseObject): self._idle_timer_expired(), f"{self}::idle_timer", ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _cancel_idle_timer(self): """Cancel the idle timer if running.""" diff --git a/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py b/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py index 1e127248f..db6ef3c00 100644 --- a/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py +++ b/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py @@ -153,6 +153,8 @@ class SpeechTimeoutUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _handle_transcription(self, frame: TranscriptionFrame): """Handle user transcription.""" @@ -174,6 +176,8 @@ class SpeechTimeoutUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) def _calculate_timeout(self) -> float: """Calculate the timeout value based on current state. diff --git a/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py b/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py index 895d0976f..2931965aa 100644 --- a/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py +++ b/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py @@ -193,6 +193,8 @@ class TurnAnalyzerUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _handle_transcription(self, frame: TranscriptionFrame): """Handle user transcription.""" @@ -217,6 +219,8 @@ class TurnAnalyzerUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _handle_prediction_result(self, result: Optional[MetricsData]): """Handle a prediction result event from the turn analyzer.""" diff --git a/src/pipecat/turns/user_turn_completion_mixin.py b/src/pipecat/turns/user_turn_completion_mixin.py index 40ae551b4..fc421e411 100644 --- a/src/pipecat/turns/user_turn_completion_mixin.py +++ b/src/pipecat/turns/user_turn_completion_mixin.py @@ -254,6 +254,8 @@ class UserTurnCompletionLLMServiceMixin: self._incomplete_timeout_handler(incomplete_type, timeout), f"_incomplete_timeout_{incomplete_type}", ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _cancel_incomplete_timeout(self): """Cancel any pending incomplete timeout task.""" From ea7324b2ba71807dd51ea4fd8a4b4455086f7483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Fri, 27 Mar 2026 19:03:55 -0700 Subject: [PATCH 2/2] Add changelog for #4183 --- changelog/4183.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/4183.fixed.md diff --git a/changelog/4183.fixed.md b/changelog/4183.fixed.md new file mode 100644 index 000000000..a1f5ad4c8 --- /dev/null +++ b/changelog/4183.fixed.md @@ -0,0 +1 @@ +- Fixed a timing issue where turn detection timer tasks (idle controller, speech timeout, turn analyzer, and turn completion) could miss their first tick because the newly created asyncio task was not yet scheduled when the caller continued.