Skip to content
Open
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
12 changes: 11 additions & 1 deletion livekit-agents/livekit/agents/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,16 @@ def delete_room(self, room_name: str | None = None) -> asyncio.Future[api.Delete
return fut

async def _delete_room() -> None:
# Disconnect locally before issuing the server-side delete so the
# rust-sdk's connection-closed flag is set before the server closes
# the publisher data channels. Without this the channels are torn
# down from the remote side while the local session is still
# "connected", which triggers spurious ERROR-level logs:
# "publisher data channel '_reliable' closed unexpectedly"
try:
await self._room.disconnect()
except Exception:
logger.exception("error disconnecting room before delete; proceeding with delete")
try:
await self.api.room.delete_room(
api.DeleteRoomRequest(room=room_name or self._room.name)
Comment on lines +639 to 645

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.

🔴 Room deletion uses the room name read after disconnecting, which may be empty

The room name is read (self._room.name at livekit-agents/livekit/agents/job.py:647) after the local disconnect has already been issued, so if the SDK clears the name on disconnect the server-side delete request is sent with an empty room identifier.

Impact: Callers that invoke delete_room() without an explicit room_name (e.g. the end-call tool) silently fail to delete the room, leaving it open.

Mechanism: room name captured after disconnect instead of before

The new code at lines 639-644 calls await self._room.disconnect() before the delete-room API call at line 646-647. When room_name is None (the default), the fallback expression room_name or self._room.name reads self._room.name after the room has been disconnected.

In the LiveKit Python SDK, Room.name is backed by the Rust FFI handle's internal state. After disconnect(), this state is typically cleared, causing name to return an empty string. Since None or "" evaluates to "", the DeleteRoomRequest is issued with room="", which will either fail or be a no-op on the server.

Callers that hit this path:

  • livekit-agents/livekit/agents/beta/tools/end_call.py:127: await job_ctx.delete_room() — no room_name argument
  • Example code like examples/voice_agents/error_callback.py:82: ctx.delete_room()

Callers that pass room_name explicitly (e.g. room_io.py:482, warm_transfer.py:252) are unaffected.

The fix is to capture the room name before disconnecting:

name = room_name or self._room.name
await self._room.disconnect()
...
await self.api.room.delete_room(api.DeleteRoomRequest(room=name))
Suggested change
try:
await self._room.disconnect()
except Exception:
logger.exception(
"error disconnecting room before delete; proceeding with delete"
)
try:
await self.api.room.delete_room(
api.DeleteRoomRequest(room=room_name or self._room.name)
try:
await self._room.disconnect()
except Exception:
logger.exception(
"error disconnecting room before delete; proceeding with delete"
)
try:
await self.api.room.delete_room(
api.DeleteRoomRequest(room=_room_name)
)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Expand Down Expand Up @@ -739,7 +749,7 @@ def transfer_sip_participant(
self._track_pending_task(task, name="transfer_sip_participant")
return task

def shutdown(self, reason: str = "user requested") -> None:
def shutdown(self, reason: str = "") -> None:
self._on_shutdown(reason)
Comment on lines +752 to 753

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.

🔍 Default shutdown reason changed from descriptive string to empty string

The default reason parameter for shutdown() was changed from "user requested" to "" at livekit-agents/livekit/agents/job.py:754. This reason string is passed through _on_shutdown and eventually to all registered shutdown callbacks (see livekit-agents/livekit/agents/ipc/job_proc_lazy_main.py:428). Any downstream code that logs or inspects the shutdown reason will now receive an empty string when shutdown() is called without arguments, which could make debugging harder. The _JobShutdownInfo dataclass at line 960 also stores this reason. If this is intentional (e.g. to distinguish programmatic shutdowns from user-initiated ones), it should be documented.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


def add_participant_entrypoint(
Expand Down
Loading