Bug Description
Bug: _user_turn_completed_task can be poisoned by _cancel_speech_pause when paused speech has no active generation
Summary
We are seeing intermittent failures where user speech is transcribed (including final transcript events), but the user turn is not committed and no assistant reply is generated.
The key runtime error is:
RuntimeError: cannot use wait_for_generation: no active generation is running.
Expected behavior
- If a user turn is finalized by STT, the turn completion path should either:
- commit the user message to chat context and continue reply flow, or
- skip reply in a controlled/explicit way without poisoning future turns.
- Pause/cancel race conditions in false-interruption handling should not raise uncaught errors from internal task chains.
- A failure in one
_cancel_speech_pause_task / _user_turn_completed_task should not repeatedly break later turns via await old_task.
Actual behavior
_cancel_speech_pause may call _wait_for_generation() on a paused speech with no active generation and raise:
RuntimeError: cannot use wait_for_generation: no active generation is running.
- The exception bubbles out of
_user_turn_completed_task.
- Subsequent turns can keep failing because new tasks await the prior failed task (
old_task) and re-raise.
Stack (representative):
Error in _user_turn_completed_task
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/livekit/agents/utils/log.py", line 17, in async_fn_logs
return await fn(*args, **kwargs)
File "/usr/local/lib/python3.11/site-packages/livekit/agents/voice/agent_activity.py", line 1537, in _user_turn_completed_task
await self._cancel_speech_pause(self._cancel_speech_pause_task)
File "/usr/local/lib/python3.11/site-packages/livekit/agents/voice/agent_activity.py", line 2834, in _cancel_speech_pause
await old_task
File "/usr/local/lib/python3.11/site-packages/livekit/agents/voice/agent_activity.py", line 2834, in _cancel_speech_pause
await old_task
File "/usr/local/lib/python3.11/site-packages/livekit/agents/voice/agent_activity.py", line 2850, in _cancel_speech_pause
await self._paused_speech._wait_for_generation()
File "/usr/local/lib/python3.11/site-packages/livekit/agents/voice/speech_handle.py", line 257, in _wait_for_generation
raise RuntimeError("cannot use wait_for_generation: no active generation is running.")
RuntimeError: cannot use wait_for_generation: no active generation is running.
Impact
- User says something; STT interim/final events are emitted.
- Turn sometimes does not get committed into conversation context.
- Assistant does not respond for that turn.
- After first failure, later turns may also fail because subsequent
_user_turn_completed_task / _cancel_speech_pause await the prior failed task (old_task) and re-raise.
Why this looks like a framework-level race
In this path, _cancel_speech_pause does:
await old_task
- If paused speech is present and interruptible:
self._paused_speech.interrupt()
await self._paused_speech._wait_for_generation()
If _paused_speech exists but its generation was never authorized/started, _wait_for_generation() raises because no active generation future exists.
Once that task fails, await old_task in the next task re-raises again, which can poison subsequent user-turn completion tasks.
Notes from our observations
- This is intermittent (not every turn).
- STT itself appears healthy (interim/final transcripts continue to arrive).
- We also saw nearby custom-app errors (
_insert_user_message missing helper method), but those exceptions are locally caught and do not share this call stack.
- Trace line numbers map to
livekit-agents 1.4.4 paths.
Environment
- Deployment: Docker on remote host
- Host OS: Ubuntu 24.04.4 LTS (Noble), kernel
6.8.0-100-generic, x86_64
- Docker:
29.2.1
- Docker Compose:
v5.1.0
- Running app image:
voice-agent:414f240
- Container Python:
3.11.15
- Voice pipeline with VAD/STT turn completion (intermittent issue under real call traffic)
LiveKit-related package versions from running interview/multiroom containers:
livekit==1.1.2
livekit-agents==1.4.4
livekit-api==1.1.0
livekit-blingfire==1.1.0
livekit-protocol==1.1.2
livekit-plugins-azure==1.4.4
livekit-plugins-cartesia==1.4.4
livekit-plugins-deepgram==1.4.4
livekit-plugins-elevenlabs==1.4.4
livekit-plugins-google==1.4.4
livekit-plugins-groq==1.4.4
livekit-plugins-noise-cancellation==0.2.5
livekit-plugins-openai==1.4.4
livekit-plugins-silero==1.4.4
livekit-plugins-turn-detector==1.4.4
Related issues checked
Questions
- Is this a known race where
_paused_speech can exist before generation authorization starts?
- Should
_cancel_speech_pause defensively avoid _wait_for_generation() when no generation is active?
- Should failed
_cancel_speech_pause_task / _user_turn_completed_task be isolated so a prior failure does not keep re-failing future turns via await old_task?
Optional mitigation we considered
Disabling false-interruption pause flow (e.g. no pause/resume path) appears to reduce frequency, but this is not acceptable for our production constraints:
- It can force unnecessary regenerate/reply cycles on interrupts that are not meaningful user turns.
- That increases both response latency and model cost.
- It degrades UX in noisy/interrupted environments where pause/resume is important.
So we are looking for a framework-side fix or guard for this race, without disabling false-interruption pause behavior.
Expected Behavior
- If a user turn is finalized by STT, the turn completion path should either:
- commit the user message to chat context and continue reply flow, or
- skip reply in a controlled/explicit way without poisoning future turns AND emit event/error that can be handled by framework user
- Pause/cancel race conditions in false-interruption handling should not raise uncaught errors from internal task chains.
- A failure in one
_cancel_speech_pause_task / _user_turn_completed_task should not repeatedly break later turns via await old_task.
Reproduction Steps
This is intermittent in real traffic, so we can use two approaches:
### A) Best-effort runtime repro (non-deterministic)
1. Use `livekit-agents==1.4.4` with false-interruption pause enabled:
- `resume_false_interruption=True`
- `false_interruption_timeout=2.0` (or similar non-None value)
- low interruption thresholds (e.g. `min_interruption_duration` small, `min_interruption_words=0`)
2. Start a normal voice session and let the agent produce a longer reply.
3. During early playout / generation, inject brief user audio activity bursts that often create interruption signals but do not always become stable semantic turns.
4. Repeat many turns (stress run). In our case it appears after many cycles, not immediately.
5. Observe occasional `_user_turn_completed_task` failures with:
- `_cancel_speech_pause -> _wait_for_generation() -> RuntimeError(\"no active generation\")`
### B) Minimal synthetic repro (deterministic branch validation)
Goal: validate the exact failing branch without depending on realtime timing.
Conceptually:
1. Put `AgentActivity` into a state where `_paused_speech` exists.
2. Ensure that paused speech has no active generation future (equivalent to generation never authorized/started or already cleared).
3. Call `_cancel_speech_pause(interrupt=True)`.
4. Observe `RuntimeError: cannot use wait_for_generation: no active generation is running.` from `speech_handle._wait_for_generation()`.
This synthetic approach may be useful because the production race is timing-sensitive and not guaranteed to reproduce on every run.
Operating System
Ubuntu
Models Used
Deepgram flux-general-en, openrouter/gemini3-flash-preview, Elevenlabs turbo 2.5
Package Versions
livekit==1.1.2
livekit-agents==1.4.4
livekit-api==1.1.0
livekit-blingfire==1.1.0
livekit-protocol==1.1.2
livekit-plugins-azure==1.4.4
livekit-plugins-cartesia==1.4.4
livekit-plugins-deepgram==1.4.4
livekit-plugins-elevenlabs==1.4.4
livekit-plugins-google==1.4.4
livekit-plugins-groq==1.4.4
livekit-plugins-noise-cancellation==0.2.5
livekit-plugins-openai==1.4.4
livekit-plugins-silero==1.4.4
livekit-plugins-turn-detector==1.4.4
Session/Room/Call IDs
room name 019cdd3e-f20b-702b-a472-401a57193177
room session id RM_8an2PRNpwamk
Proposed Solution
Additional Context
No response
Screenshots and Recordings
No response
Bug Description
Bug:
_user_turn_completed_taskcan be poisoned by_cancel_speech_pausewhen paused speech has no active generationSummary
We are seeing intermittent failures where user speech is transcribed (including final transcript events), but the user turn is not committed and no assistant reply is generated.
The key runtime error is:
Expected behavior
_cancel_speech_pause_task/_user_turn_completed_taskshould not repeatedly break later turns viaawait old_task.Actual behavior
_cancel_speech_pausemay call_wait_for_generation()on a paused speech with no active generation and raise:RuntimeError: cannot use wait_for_generation: no active generation is running._user_turn_completed_task.old_task) and re-raise.Stack (representative):
Impact
_user_turn_completed_task/_cancel_speech_pauseawait the prior failed task (old_task) and re-raise.Why this looks like a framework-level race
In this path,
_cancel_speech_pausedoes:await old_taskself._paused_speech.interrupt()await self._paused_speech._wait_for_generation()If
_paused_speechexists but its generation was never authorized/started,_wait_for_generation()raises because no active generation future exists.Once that task fails,
await old_taskin the next task re-raises again, which can poison subsequent user-turn completion tasks.Notes from our observations
_insert_user_messagemissing helper method), but those exceptions are locally caught and do not share this call stack.livekit-agents 1.4.4paths.Environment
6.8.0-100-generic,x86_6429.2.1v5.1.0voice-agent:414f2403.11.15LiveKit-related package versions from running interview/multiroom containers:
Related issues checked
mark_generation_donevariant in older versions, but this iswait_for_generationin_cancel_speech_pause:Questions
_paused_speechcan exist before generation authorization starts?_cancel_speech_pausedefensively avoid_wait_for_generation()when no generation is active?_cancel_speech_pause_task/_user_turn_completed_taskbe isolated so a prior failure does not keep re-failing future turns viaawait old_task?Optional mitigation we considered
Disabling false-interruption pause flow (e.g. no pause/resume path) appears to reduce frequency, but this is not acceptable for our production constraints:
So we are looking for a framework-side fix or guard for this race, without disabling false-interruption pause behavior.
Expected Behavior
_cancel_speech_pause_task/_user_turn_completed_taskshould not repeatedly break later turns viaawait old_task.Reproduction Steps
Operating System
Ubuntu
Models Used
Deepgram flux-general-en, openrouter/gemini3-flash-preview, Elevenlabs turbo 2.5
Package Versions
Session/Room/Call IDs
room name 019cdd3e-f20b-702b-a472-401a57193177
room session id RM_8an2PRNpwamk
Proposed Solution
Additional Context
No response
Screenshots and Recordings
No response