Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions livekit-agents/livekit/agents/voice/avatar/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,40 @@ async def start(self, agent_session: AgentSession, room: rtc.Room) -> None:
else:
self._room.on("connection_state_changed", self._on_connection_state_changed)

async def wait_for_join(self, *, timeout: float | None = 30.0) -> None:
"""Wait until the avatar participant has joined the room and
published its video track.

Raises ``asyncio.TimeoutError`` if it doesn't arrive within
``timeout`` seconds. Pass ``timeout=None`` to wait indefinitely.
"""
if self._wait_avatar_join_task is None:
return
Comment on lines +122 to +123
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.

πŸ”΄ wait_for_join silently returns before avatar joins when room is not yet connected

When start() is called with a disconnected room (lines 110-113), _wait_avatar_join_task is not created immediately β€” only a connection_state_changed listener is registered. The task is created later in _on_connection_state_changed (line 203-204) when the room actually connects. However, wait_for_join() at line 122-123 returns immediately if _wait_avatar_join_task is None, which silently signals success to the caller even though the avatar has not joined. This violates the method's documented contract to "wait until the avatar participant has joined the room and published its video track."

Prompt for agents
The wait_for_join method returns immediately when _wait_avatar_join_task is None, but this state occurs legitimately when start() was called with a disconnected room (the task is only created later by _on_connection_state_changed). The fix should make wait_for_join able to wait for the task to be created. One approach: introduce an asyncio.Event (e.g. self._avatar_joined_event) that is set at the end of _wait_avatar_join(), and have wait_for_join await that event (with the specified timeout) instead of directly awaiting the task. This handles both the connected and not-yet-connected cases correctly. Alternatively, you could make the task always created in start() (even for disconnected rooms, the task itself would first wait for connection).
Open in Devin Review

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

if timeout is None:
await self._wait_avatar_join_task
return
await asyncio.wait_for(asyncio.shield(self._wait_avatar_join_task), timeout=timeout)

async def aclose(self) -> None:
if self._room is not None and self._room.isconnected():
job_ctx = get_job_context(required=False)
if job_ctx is not None:
try:
from livekit import api

await job_ctx.api.room.remove_participant(
api.RoomParticipantIdentity(
room=self._room.name,
identity=self.avatar_identity,
)
)
except Exception:
logger.warning(
"failed to remove avatar participant",
extra={"identity": self.avatar_identity},
exc_info=True,
)

if self._agent_session:
self._agent_session.off("conversation_item_added", self._on_conversation_item_added)
self._agent_session = None
Expand Down
Loading