Skip to content

Bug: _user_turn_completed_task can be poisoned by _cancel_speech_pause when paused speech has no active generation #5100

Description

@Lin-2357

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:

  1. await old_task
  2. If paused speech is present and interruptible: self._paused_speech.interrupt()
  3. 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

  1. Is this a known race where _paused_speech can exist before generation authorization starts?
  2. Should _cancel_speech_pause defensively avoid _wait_for_generation() when no generation is active?
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions