Skip to content

fix(voice): preserve transcript flush during session close - #6843

Open
IanCollection wants to merge 3 commits into
livekit:mainfrom
IanCollection:agent/preserve-close-transcript-flush
Open

fix(voice): preserve transcript flush during session close#6843
IanCollection wants to merge 3 commits into
livekit:mainfrom
IanCollection:agent/preserve-close-transcript-flush

Conversation

@IanCollection

Copy link
Copy Markdown

What changed

  • distinguish the transcript flush explicitly started by normal AgentSession close from ordinary commit/EOU tasks
  • drain that close-owned commit and its resulting EOU task instead of cancelling them
  • keep the remaining audio-recognition teardown cancellation-safe and surface close-flush failures after cleanup

Why

Normal session close starts _commit_user_turn(...) with session_close_transcript_timeout and immediately closes the activity. AudioRecognition._aclose() then cancelled the same commit task (and its EOU task), so an interim/final transcript still being flushed could never reach conversation history.

Only the exact tasks owned by normal session close are preserved. Public commits, handoff teardown, and error-close tasks retain the existing cancellation behavior. If outer cancellation arrives, cleanup is reaped before CancelledError is propagated.

Impact

The last user utterance can finish committing during normal session shutdown, so session_close_transcript_timeout again provides its intended transcript-capture behavior without broadening waits on unrelated close paths.

Validation

  • fail-before regression: a real AgentSession close emitted no final transcript/history item for a pending interim transcript
  • direct assertions cover both agent chat context and session history, exactly once
  • cancellation cleanup, ordinary-task cancellation, and close-owned child-error regressions included
  • pytest tests/test_audio_recognition_aclose.py tests/test_agent_session.py --unit -q β€” 92 passed
  • handoff selection β€” 13 passed
  • Ruff check and format check passed
  • mypy (livekit.agents) β€” 206 files passed

@CLAassistant

CLAassistant commented Aug 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@IanCollection
IanCollection marked this pull request as ready for review August 17, 2026 07:42
@IanCollection
IanCollection requested a review from a team as a code owner August 17, 2026 07:42

@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 found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +859 to +860
if flush_error is not None:
raise flush_error

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.

🟑 A failure while saving the last spoken words aborts the rest of session shutdown

An error raised while finishing the last transcript is re-thrown out of audio-recognition teardown (raise flush_error at livekit-agents/livekit/agents/voice/audio_recognition.py:860) instead of being logged, so the remaining shutdown steps never run and the session is left half-closed.

Impact: If saving the final user utterance fails, the room connection is never released and applications never receive the session-closed notification.

How the error escapes and which teardown steps get skipped

Before this PR the close-owned commit task was always cancelled, so any exception it raised was discarded. Now _finish_close_flush records it into flush_error and _cleanup re-raises it, which propagates out of AudioRecognition._aclose() β†’ AgentActivity._close_session() (livekit-agents/livekit/agents/voice/agent_activity.py:1320-1321) β†’ AgentActivity.aclose() β†’ AgentSession._aclose_impl() at livekit-agents/livekit/agents/voice/agent_session.py:1253.

Everything after that await activity.aclose() is skipped: self._activity = None, span ends, self._started = False, self.emit("close", CloseEvent(...)), self._session_host.aclose() and self._room_io.aclose(). _aclose_impl is wrapped in @utils.log_exceptions, so the failure is only logged and the session stays in a started-but-broken state with room I/O still open.

The commit coroutine is not wrapped in log_exceptions (livekit-agents/livekit/agents/voice/audio_recognition.py:1054) and calls user-visible hooks (_hooks.on_final_transcript, _hooks.retrieve_chat_ctx, _run_eou_detection), any of which can raise.

Logging the flush failure (as the surrounding teardown does for other best-effort work) instead of re-raising would keep the rest of the session shutdown intact.

Suggested change
if flush_error is not None:
raise flush_error
if flush_error is not None:
logger.error(
"transcript flush failed during session close", exc_info=flush_error
)
Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

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