Skip to content

fix(voice): force-interrupt outdated reply in _user_turn_completed_task#6451

Open
mpg-kaoru-hotate wants to merge 1 commit into
livekit:mainfrom
mpg-kaoru-hotate:fix/force-interrupt-outdated-user-turn-reply
Open

fix(voice): force-interrupt outdated reply in _user_turn_completed_task#6451
mpg-kaoru-hotate wants to merge 1 commit into
livekit:mainfrom
mpg-kaoru-hotate:fix/force-interrupt-outdated-user-turn-reply

Conversation

@mpg-kaoru-hotate

Copy link
Copy Markdown

Fixes #4443

Root cause

When a new user turn starts while a previous _user_turn_completed_task is still running, the older task detects it is outdated and discards its reply:

if self._user_turn_completed_atask != asyncio.current_task():
    # If a new user turn has already started, interrupt this one since it's now outdated
    await speech_handle.interrupt()

speech_handle was just created by _generate_reply() and inherits the session's allow_interruptions. When interruptions are disabled (turn_handling={"interruption": {"enabled": False}}, or Agent(allow_interruptions=False)), interrupt() without force=True raises:

RuntimeError: This generation handle does not allow interruptions

Consequences beyond the noisy error log:

  • the remainder of the task is aborted, so EOU metrics for that turn are never emitted
  • the outdated reply handle is not cancelled and stays scheduled, so the agent can play a reply to a partial/stale transcript alongside the reply of the newer turn

This is internal cleanup of a reply that should never play — not a user-initiated interruption — so it should bypass the allow_interruptions guard with force=True. All other internal interrupt() call sites in agent_activity.py already either pass force=True or check allow_interruptions first; this was the only unguarded one.

Reproduction

We hit this in production (telephony agent with interruptions disabled to suppress line-echo false positives) whenever the user produced consecutive turns in quick succession — e.g. speaking again right after end-of-turn was detected, or STT splitting one utterance into multiple finals. Traceback from Sentry matches the one reported in #4443:

  File ".../livekit/agents/voice/agent_activity.py", line 1983, in _user_turn_completed_task
    await old_task
  ...
  File ".../livekit/agents/voice/agent_activity.py", line 2127, in _user_turn_completed_task
    await speech_handle.interrupt()
  File ".../livekit/agents/voice/speech_handle.py", line 151, in interrupt
    raise RuntimeError("This generation handle does not allow interruptions")

The added regression test (test_consecutive_user_turns_when_interruptions_disabled) reproduces exactly this traceback on main without the fix, and passes with it. It mirrors the existing test_interrupt_during_on_user_turn_completed scenario (second user turn arriving while on_user_turn_completed of the first turn is still running) with interruption.enabled=False.

Testing

  • pytest tests/test_agent_session.py — 55 passed (including the 2 new parametrized cases)
  • ruff check / ruff format clean

🤖 Generated with Claude Code

When a new user turn starts while a previous _user_turn_completed_task
is still running, the older task discards its now-outdated reply via
speech_handle.interrupt(). The handle inherits the session's
allow_interruptions, so with interruption disabled
(turn_handling.interruption.enabled=False) this internal cleanup raised
RuntimeError("This generation handle does not allow interruptions"),
aborted the rest of the task (EOU metrics), and left the outdated reply
scheduled for playout.

Use interrupt(force=True): this is internal cleanup of a reply that
should never play, not a user-initiated interruption.

Fixes livekit#4443

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mpg-kaoru-hotate
mpg-kaoru-hotate requested a review from a team as a code owner July 16, 2026 02:48
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@longcw longcw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks for the pr! could you sign t he CLA before we merge it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RuntimeError: This generation handle does not allow interruptions

3 participants