fix: prevent _cancel_speech_pause from poisoning subsequent user turns - #5101
Merged
longcw merged 1 commit intoMar 13, 2026
Conversation
Guard _wait_for_generation() call — only invoke it when the paused speech actually has active generations (self._paused_speech._generations is non-empty). A paused speech that was never authorized or whose generation was cancelled before starting will have an empty _generations list, and calling _wait_for_generation() on it raises RuntimeError. Additionally, wrap 'await old_task' in try/except so a failed prior _cancel_speech_pause task does not keep re-raising through subsequent tasks that chain on it. Without this, one race-condition failure permanently poisons the task chain and all subsequent user turns silently fail (no assistant reply generated). Closes #5100 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
giulio-leone seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
longcw
approved these changes
Mar 13, 2026
iancarrasco-b10
pushed a commit
to iancarrasco-b10/agents
that referenced
this pull request
Mar 17, 2026
livekit#5101) Co-authored-by: giulio-leone <giulio.leone@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
russellmartin-livekit
pushed a commit
that referenced
this pull request
Apr 13, 2026
#5101) Co-authored-by: giulio-leone <giulio.leone@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a race condition where
_cancel_speech_pausecan permanently break user-turn processing, causing the assistant to silently stop replying.Problem
When a user interrupts paused speech that has no active generation (e.g., the generation was never authorized or was cancelled before starting),
_wait_for_generation()raises:This error bubbles up from
_cancel_speech_pauseand poisons the_cancel_speech_pause_task. Since subsequent tasks chain on the prior task viaawait old_task, the RuntimeError re-raises in every subsequent call, permanently breaking user-turn completion.Impact: User speech is transcribed (STT events arrive) but the turn is never committed to chat context and no assistant reply is generated — for the current turn and all future turns.
Root Cause
In
_cancel_speech_pause:await old_task— re-raises any error from a previously failed taskawait self._paused_speech._wait_for_generation()— raisesRuntimeErrorifself._generationsis emptyA paused speech handle with an empty
_generationslist triggers (2), which then triggers (1) on every subsequent call.Fix
Two changes to
_cancel_speech_pause:Guard
_wait_for_generation(): Only call it whenself._paused_speech._generationsis non-empty. This prevents the RuntimeError from being raised in the first place.Catch errors from
await old_task: Wrap it in try/except so a single failed task doesn't permanently poison the task chain. The error is logged at debug level and subsequent turns can proceed normally.Testing
The fix is defensive — it prevents an uncaught exception from a known race condition. The scenario requires specific timing between STT events, pause/resume, and generation authorization that is difficult to unit-test deterministically. The fix follows the same defensive pattern used in
_mark_done()(which usescontextlib.suppressfor similar edge cases).Closes #5100