Repository navigation
Description
Bug Description
AgentSession._on_error applies an max_unrecoverable_errors tolerance counter for llm_error and tts_error, but stt_error has no such counter — it falls through and immediately closes the session on the first non-recoverable STT error.
Root Cause
In livekit-agents/livekit/agents/voice/agent_session.py, the _on_error method:
def _on_error(self, error: llm.LLMError | stt.STTError | tts.TTSError | ...) -> None:
if self._closing_task or error.recoverable:
return
if error.type == "llm_error":
self._llm_error_counts += 1
if self._llm_error_counts <= self.conn_options.max_unrecoverable_errors:
return # ← tolerance applied
elif error.type == "tts_error":
self._tts_error_counts += 1
if self._tts_error_counts <= self.conn_options.max_unrecoverable_errors:
return # ← tolerance applied
# stt_error: no branch → falls through → session closes immediately ❌
self._closing_task = asyncio.create_task(
self._aclose_impl(error=error, reason=CloseReason.ERROR)
)_llm_error_counts and _tts_error_counts are initialised and reset in multiple places (__init__, _aclose_impl, _update_agent_state), but there is no equivalent _stt_error_counts.
Real-World Impact
When an STT provider returns transient errors (e.g. HTTP 403/429 from Groq after exhausting per-utterance retries in the RecognizeStream._main_task loop), the session closes immediately on the very first non-recoverable STT error. There is no opportunity for application-level error handlers (registered via session.on("error", ...)) to perform graceful recovery such as transferring to a human agent.
The per-utterance retry loop in RecognizeStream._main_task already handles transient network blips. The session-level tolerance is meant to handle the case where retries are truly exhausted — but currently STT gets zero tolerance at the session level while LLM and TTS each get max_unrecoverable_errors (default 3).
Expected Behaviour
stt_error should receive the same tolerance as llm_error and tts_error: allow up to max_unrecoverable_errors non-recoverable STT errors before closing the session, giving application error handlers a chance to act (e.g. fall back to another STT, transfer to human, speak an acknowledgement).
Proposed Fix
Add _stt_error_counts counter mirroring the existing LLM/TTS pattern:
# __init__ and reset sites:
self._stt_error_counts = 0
# _on_error:
elif error.type == "stt_error":
self._stt_error_counts += 1
if self._stt_error_counts <= self.conn_options.max_unrecoverable_errors:
returnAnd reset _stt_error_counts alongside the other counters in _aclose_impl and _update_agent_state (when state transitions to "speaking").
Environment
livekit-agents(latest main)- STT plugin:
livekit-plugins-groq(wrapslivekit-plugins-openaiSTT) - Reproduced with any STT provider that returns HTTP 4xx errors after retries are exhausted
Activity
longcw commentedon Jul 14, 2026
Unlike LLM/TTS, which open a fresh stream every turn, the STT stream is long-lived: it is created once per agent activity. So after the first unrecoverable error there is no STT at all for the rest of the session. For LLM/TTS the tolerance meaningfully tracks consecutive failed turns because each turn makes a new stream; for STT there is no next stream, so the count freezes at 1 and never advances.
I'll open a PR with the pipeline-level reconnect plus the session counter.
AKomplished-bug commentedon Jul 14, 2026
Thanks @longcw , I've been wanting to contribute to LiveKit for a while, and since I ran into this one in production it feels like a good place to start. Would you mind if I took a shot at the PR — the reconnect plus the counter? Totally understand if you'd rather run with it since you're already across the details, just thought I'd offer.