Repository navigation
fix(voice): don't hang or buffer audio when the recorder encoder fails - #7741
Merged
longcw merged 2 commits intoOct 12, 2026
Merged
Conversation
The encode thread created the output directory, opened the container and added the stream before its try block. When any of those raised (an unwritable session directory, a missing codec), the thread died without reaching the finally that resolves _close_fut, so RecorderIO.aclose() waited forever and with it AgentSession.aclose() and job shutdown. Setup now runs inside the try, and the container is entered before the stream is added so a failed add_stream still closes the file. Once the encode thread has exited nothing drains the queue, yet every input and played frame kept being queued for the rest of the session. All puts now go through _put, which drops items after _close_fut is done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oder failure - RecorderAudioOutput kept accumulating each segment's frames after the encode thread had exited. It now checks the same _encoding condition that guards the queue. - start() creates a fresh queue, so audio left in the queue by a run whose encoder failed cannot be encoded on the next run's timeline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ANDROIDHASSAN
added a commit
to ANDROIDHASSAN/agents
that referenced
this pull request
Oct 12, 2026
…trate Resolve conflicts with livekit#7741: apply bit_rate and vbr inside the encode thread's try block, and keep both sets of tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ANDROIDHASSAN
added a commit
to ANDROIDHASSAN/agents
that referenced
this pull request
Oct 13, 2026
…trate Resolve conflicts with livekit#7741: apply bit_rate and vbr inside the encode thread's try block, and keep both sets of tests.
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.
Problem:
RecorderIO._encode_threadcreates the output directory, opens the Ogg container and adds the Opus stream before itstry. If any of those raise (an unwritable or misconfigured session directory, a codec that can't be added), the thread dies without reaching thefinallythat resolves_close_fut.RecorderIO.aclose()then waits on_close_futforever, and so doAgentSession.aclose()and the job shutdown injob.pythat awaits it.A second problem compounds the first: once the encode thread has exited, nothing drains
self._q, buton_frameandon_playedkeep queueing every input and played frame for the rest of the session, so the audio piles up in memory until the process ends.Fix:
try, so a failure is logged by the existing handler and thefinallystill resolves_close_fut. The container is entered withwith av.open(...)before the stream is added, so a failedadd_streamstill closes the file._put, which drops items once_close_futis done, which is when the encode thread has exited.RecorderAudioOutput.capture_framechecks the same condition, so the agent side stops buffering segments too.start()creates a fresh queue, so audio left behind by a run whose encoder failed can't reach the next run's timeline.Most of the source diff is re-indentation.
git diff -wshows about 15 changed lines.Tests
Three new cases in
tests/test_recorder_io.py. Both point the recorder at a path whose parent is a regular file, somkdirraises:test_a_failed_encoder_setup_does_not_hang_close:aclose()must return within 5 s. Onmainit times out, and pytest reports the encode thread's unhandledFileExistsError.test_a_failed_encoder_stops_buffering_audio: after the encode thread exits, ten more input frames must not grow the queue, and an agent frame must not open a segment buffer. Onmainthis test errors, because the encode thread dies unhandled andaclose()never returns.test_a_restart_does_not_record_audio_left_over_from_a_failed_run: a frame left in a failed run's queue must not appear in the next run's file.ruff format,ruff checkandmake type-checkare clean.Scope: only
recorder_io.py.recordingstill reportsTrueafter a failure, sojob.pystill callsaclose(), which now returns. This PR does not change that behaviour. #7740 touches the codec setup lines in the same function. Whichever PR merges second needs a small rebase, and I'll do it.🤖 Generated with Claude Code