fix(voice): discard the stale user turn when resuming a false interruption - #7067
Open
saime428 wants to merge 1 commit into
Open
fix(voice): discard the stale user turn when resuming a false interruption#7067saime428 wants to merge 1 commit into
saime428 wants to merge 1 commit into
Conversation
…ption The recognition turn that triggered a false interruption is abandoned on resume but never cleared, so its speech anchors survive and the next real utterance reuses them. started_speaking_at then reports a turn the system had already decided was not real. Clear it in the resume branch, before the state restore marks the agent speaking again. Two things it does not do: - the stt stream is kept alive (reset_stt=False). Recreating it drops audio the provider is still decoding, and when the barge-in was real and vad missed it, that late final is the only thing that can still interrupt the resumed speech. - the discard is skipped while speech is still tracked. In stt turn detection an stt end of speech arms this timer while vad is mid-segment, and those anchors belong to the utterance in progress. Fixes livekit#7063. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
saime428
force-pushed
the
fix/false-interruption-stale-user-turn
branch
from
September 7, 2026 11:47
38b3f09 to
f50da9e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7063.
When a false interruption is resumed, the recognition turn that caused it is abandoned but
never cleared. Its speech anchors survive, and the next real utterance reuses them, so
started_speaking_atis stamped from a turn the system already decided was not real.The discard goes in the resume branch, before the state restore marks the agent speaking
again.
Updated after the review on #7063
@dorukdumlu compared the three open attempts at this
bug and raised two
problems with this PR's original version. Both were fair, and this PR now addresses them.
The STT stream is no longer torn down. The original called
_clear_user_turn(), whichends by recreating the provider stream (
_update_stt(None)and back). That is costly onevery false interruption, and it drops audio the provider is still decoding. When the
barge-in was real and VAD missed it, that late final is the only thing left that can
interrupt the resumed speech.
_clear_user_turnnow takesreset_stt, and the resumepasses
reset_stt=False.The discard is skipped while speech is still tracked. The original cleared
unconditionally, which could take a live utterance's anchors.
On the second point I could confirm the problem but not the path given for it. A VAD segment
below
min_durationdoes not leave the timer armed with live anchors:_on_vad_eventsets_speech_start_timeand_vad_speech_startedand then callson_start_of_speechin thesame synchronous block, and
on_start_of_speechcancels the timer.The path that does reach it is STT turn detection. An STT
END_OF_SPEECHcallson_end_of_speech, which arms this timer, while VAD is still inside its speech segment.audio_recognitionalready knows about that state and warns on it:Those anchors belong to the utterance in progress, so the resume now leaves them alone. The
guard is
_speaking or _vad_speech_started, and in the ordinary false-interruption case bothare already
False: the VADEND_OF_SPEECHthat armed the timer released them, leaving onlythe anchor behind.
Relationship to #7066 and #7101
All three PRs fix the reported bug. #7066 and #7101 are more thorough; this one is the
smallest change that covers the same ground, at 32 lines of source. It has no
decided/undecided split and adds no new state, so if either of the others is the preferred
base, take it and close this.
Tests
test_resume_discards_the_stale_recognition_turnis the regression test for the report. Itsoriginal version set
_vad_speech_started = True, which is not the state the timer actuallyfires in, so it now models the real one: the VAD
END_OF_SPEECHhas already released_speakingand_vad_speech_started, and only the anchor is left.Two tests are added for the changes above:
test_resume_keeps_the_anchors_of_a_live_utterancecovers the STT case where the discard must be skipped, and
test_resume_keeps_the_stt_stream_alivefails if the discard goes back to recreating theprovider stream.
Validation
Rebased onto
4d03f50.uv run pytest tests/test_false_interruption_resume.py --unit: 12 passedThe regression test fails on a clean
4d03f50checkout without this change, atassert recognition._speech_start_time is Noneuv run pytest tests/ --unit, same machine, run back to back:4d03f50The three extra passes are the tests above.
tests/has pre-existing flaky failures andteardown errors on my machine, plus a pytest
INTERNALERROR: Event loop is closedraisedfrom the concurrency plugin's teardown, so I ran a clean checkout side by side rather than
reading those as regressions. That
INTERNALERRORaborts the run before pytest prints itssummary, which is why I can give the counts but not the names of the two failures.