Skip to content

tts.FallbackAdapter forwards child error events with recoverable: false, closing the AgentSession even though the adapter is switching to the next TTS #2399

Description

@ralonsom

Version: @livekit/agents 1.7.1 (also present on main)

What happens
FallbackAdapter.setupEventForwarding() (dist/tts/fallback_adapter.js) re-emits every child TTS error event verbatim:

tts.on("error", (error) => { this.emit("error", error); });

When the primary TTS fails with a non-retryable APIStatusError (e.g. ElevenLabs 401 payment_required), the child's stream emits { type: 'tts_error', recoverable: false }. FallbackChunkedStream.run / FallbackSynthesizeStream.run catch that same APIError, log "TTS failed, switching to next instance" and mark the instance unavailable — but the forwarded recoverable: false event has already reached AgentSession, which logs "AgentSession is closing due to an unrecoverable error" and tears the session down. The fallback instance never gets to speak.

Observed 2026-09-01 in production with ElevenLabs primary + Inworld fallback: 100% of calls dropped while ElevenLabs returned 401, despite the configured fallback.

Expected
While another instance in the chain is still available, a child's non-retryable error should be surfaced as recoverable (the adapter IS recovering), and only become unrecoverable once every instance has failed (the adapter already throws all TTS instances failed in that case).

Suggested fix

tts.on("error", (error) => {
  const idx = this.ttsInstances.indexOf(tts);
  const othersAvailable = this._status.some((s, i) => i !== idx && s.available);
  if (othersAvailable && error && typeof error === "object" && error.recoverable === false) {
    this.emit("error", { ...error, recoverable: true });
    return;
  }
  this.emit("error", error);
});

(The LLM FallbackAdapter does not have this problem: it does not forward child errors; tryGenerate captures them.)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions