Skip to content

fix(cartesia): ignore stale frames from previous contexts on pooled websockets - #7081

Merged
chenghao-mou merged 1 commit into
livekit:mainfrom
Panmax:fix/cartesia-stale-context-frames
Sep 1, 2026
Merged

fix(cartesia): ignore stale frames from previous contexts on pooled websockets#7081
chenghao-mou merged 1 commit into
livekit:mainfrom
Panmax:fix/cartesia-stale-context-frames

Conversation

@Panmax

@Panmax Panmax commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

When the ConnectionPool returns a previously-used websocket, the socket buffer may still contain unread audio frames / done from the abandoned context (occurring when a SynthesizeStream was interrupted before the frames were fully consumed). The _recv_task did not validate the context_id on received messages, so stale audio was pushed into the new output and a stale "done" could prematurely truncate the new synthesis.

Changes

  • tts.py (_recv_task): skip any message whose context_id is present but does not match the current cartesia_context_id. Messages without a context_id (e.g. server errors) are not affected.
  • test_plugin_cartesia_tts.py: new test that simulates a reused socket where stale audio + done arrive before the legitimate frames, and asserts they are silently dropped.

Reproduction

Without the fix, sending a stale-context audio frame + done then current-context audio + done:

frames = [stale b"""*160, current b"\x01"*160]   # stale audio played

With the fix:

frames = [current b"\x01"*160]                     # stale frame silently dropped

Related

Root cause analysis: In the production session, 27 consecutive say() calls had ttfb≈0.002s (impossible over a fresh connection) and duration≈0.01s, confirming the socket was replaying stale frames instead of performing fresh synthesis. The "..." SPEAK text that triggered the visible symptom was just the most obvious case — any text sent through a pooled socket after an interrupted stream would exhibit the same corruption.

@Panmax
Panmax requested a review from a team as a code owner September 1, 2026 09:40

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

…ebsockets

When the ConnectionPool returns a previously-used websocket, the socket
buffer may still contain unread audio frames / done from the abandoned
context (occurring when a SynthesizeStream was interrupted before the
frames were fully consumed). The _recv_task did not validate the
context_id on received messages, so stale audio was pushed into the new
output and a stale "done" could prematurely truncate the new synthesis.

- Add context_id check in _recv_task: skip any message whose context_id
  is present but does not match the current cartesia_context_id.
  Messages without a context_id (e.g. server errors) are not affected.
- The current segment is only started from the first matching message.

The unit test simulates a reused socket where stale (wrong context_id)
audio + done arrive before the legitimate frames and asserts they are
silently dropped.
@Panmax
Panmax force-pushed the fix/cartesia-stale-context-frames branch from af7fc26 to 56dadab Compare September 1, 2026 09:46

@chenghao-mou chenghao-mou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. Thanks for the PR!

@chenghao-mou
chenghao-mou merged commit 614ef04 into livekit:main Sep 1, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants