Skip to content

RuntimeError in AgentTask.__await_impl finally block when SpeechHandle is already interrupted #4924

Description

@simllll

Description

After upgrading to a version that includes #4730, we're seeing a RuntimeError when a SpeechHandle gets interrupted during an AgentTask execution. The __await_impl finally block tries to restore speech_handle.allow_interruptions without checking if the handle is already interrupted.

Error

RuntimeError: Cannot set allow_interruptions to False, the SpeechHandle is already interrupted

Traceback (most recent call last):
  File ".venv/lib/python3.14/site-packages/livekit/agents/voice/generation.py", line 609, in _traceable_fnc_tool
    val = await function_callable()
  ...
  File ".venv/lib/python3.14/site-packages/livekit/agents/voice/agent.py", line 832, in __await_impl
    speech_handle.allow_interruptions = old_allow_interruptions
  File ".venv/lib/python3.14/site-packages/livekit/agents/voice/speech_handle.py", line 128, in allow_interruptions
    raise RuntimeError(
        "Cannot set allow_interruptions to False, the SpeechHandle is already interrupted"
    )

Context

We await prebuilt AgentTask subclasses (e.g. GetEmailTask, GetListTask) inside a function tool. The SDK correctly sets up inline_task=True on the tool's asyncio task via _set_activity_task_info in generation.py.

The issue is in __await_impl's finally block which unconditionally restores allow_interruptions:

# agent.py, __await_impl finally block
if speech_handle:
    speech_handle.allow_interruptions = old_allow_interruptions

If the user interrupts during the task (or the handle is force-interrupted via cancel() or the new _schedule_speech interrupt paths added in #4730), the handle is already in an interrupted state and the setter raises RuntimeError.

Likely cause

PR #4730 added several new paths that force-interrupt speech handles:

  1. AgentTask.cancel() calls self._activity.interrupt(force=True)
  2. _schedule_speech now calls speech.interrupt(force=True) before raising when scheduling is paused/draining
  3. _aclose_impl loops through active AgentTasks and calls cancel()

These new interruption paths can leave a speech handle in an interrupted state when __await_impl's finally block runs, but the finally block doesn't guard against this.

Suggested fix

Guard the restoration in the finally block:

if speech_handle and not speech_handle.interrupted:
    speech_handle.allow_interruptions = old_allow_interruptions

Versions

  • livekit-agents: 1.4.3
  • Python: 3.14

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions