Agent Disconnects After session.shutdown(drain=True) but User Remains Stuck in Room — Production Issue

I am using the following setup as recommended, and I have also shifted my API call from on_session_end to @session.on("close"):

  1. session.shutdown(drain=True)

  2. delete_room_on_close=False

What is happening:

When the AI calls the end_interview function, it triggers session.shutdown(drain=True). After this, I can see in the LiveKit dashboard that the agent disconnects successfully, but the user remains in the room with a stuck UI. The user has no idea what happened and thinks the interface has frozen.

What I need:

Could you please suggest a reliable solution that handles this cleanly? I need the user to be properly disconnected or shown a clear end state when the agent shuts down.

This is running in production and I cannot afford unexpected behavior like this. A solution that is stable and has no edge cases would be greatly appreciated.

Code Sample:

@llm.function_tool(description=end_interview_instruction)
async def end_interview():

logger.info("\n 🛑 TOOL CALLED: end_interview")

ctx.agent.end_reason = "assistant-said-end-call-phrase"

await session.say("Thank you for your time. Goodbye!", allow_interruptions=False)

session.shutdown(drain=True)
    await session.start(

        room=ctx.room,

agent=InterviewAgent(instructions=instructions, ctx=ctx),

room_options=room_io.RoomOptions(

audio_input=room_io.AudioInputOptions(

noise_cancellation=noise_cancellation.BVC(),

            ),

#forcefully close room and monitor sync API Call

delete_room_on_close=False,

        ),

# record=True, // it is recording file in server

    )

You can see in image

  1. agent-AJ_a4vv4RoXScHB left at “Mar 23, 2026, 3:06:48 PM“
  2. Why Room ended at “Mar 23, 2026, 3:22:01 PM“

The issue started from today

You should be able to listen for the isFinished state on your client: Agent state | LiveKit Documentation

I am depending upon the handleDisconnected function

 <LiveKitRoom

          serverUrl={liveKitUrl}

token={token}

connect={true}

audio={{ deviceId: selectedDevice }}

video={false}

onConnected={handleConnected}

onDisconnected={handleDisconnected}

onError={handleError}

onMediaDeviceFailure={handleMediaDeviceFailure}

>

I have changed my code, and now I am using
delete_room_on_close = True
When the interview ends via the tool function in Python, the room is deleted and my handleDisconnected function runs correctly.

However, when I set delete_room_on_close=False, the agent disconnects at the end of the interview but handleDisconnected is not being called on the frontend.

Could you confirm whether my current approach is correct and production ready? And if delete_room_on_close=False it is needed, what is the recommended way to ensure the frontend receives a disconnection event when I call session.shutdown()

`async def end_interview():
    logger.info("\n 🛑 TOOL CALLED: end_interview")
    ctx.agent.end_reason = "assistant-said-end-call-phrase"
    await session.say("Thank you for your time. Goodbye!", allow_interruptions=False)
    session.shutdown(drain=True)`


`await session.start(
    room=ctx.room,
    agent=InterviewAgent(instructions=instructions, ctx=ctx),
    room_options=room_io.RoomOptions(
        audio_input=room_io.AudioInputOptions(
            noise_cancellation=noise_cancellation.BVC(),
        ),
        #forcefully close room and monitor sync API Call
        delete_room_on_close=True,
    ),
    # record=True, // it is recording file in server
)`

`@session.on("close")
def on_closed(session):
    logger.info("\n ------ Cleaning up timeout task on session close ------")
    logger.info(f"Session closed Usage: {usage.get_summary()}")
    try:
        json_data = get_session_report_json(ctx)
        if environment != "local":
            logger.info( f" Close event: {json.dumps(json_data, default=str)}")

        # 🚀 Fire the webhook immediately when the session closes
        # Create a background task so it doesn't block the event loop
        asyncio.create_task(send_session_report(ctx))
    except Exception as e:
        logger.error(f"Error getting session report: {e}")
    timeout_task.cancel()`

I want to confirm the following three things:

1. Correct use case of delete_room_on_close Which option is recommended for an interview session — True or False? What is the intended use case for each?

2. Guaranteed @session.on("close") callback after session.shutdown() When I call session.shutdown(), I need to guarantee that the following callback is always triggered:
@session.on(“close”)
def on_closed(session):

I am using this callback to send the chat history to my backend API. Could you confirm this is reliable? Or is there a better approach such as a webhook to ensure the chat history and session report are always delivered to my backend without any missed calls?

3. Frontend onDisconnected callback after session.shutdown() When session.shutdown() is called from the agent, I need the frontend LiveKit SDK to reliably trigger the onDisconnected callback from <LiveKitRoom/>.
Could you confirm this is the expected behaviour and suggest the correct way to ensure it always fires?
The
current setup of the agent session has
delete_room_on_close=True,

However, when I set delete_room_on_close=False, the agent disconnects at the end of the interview but handleDisconnected is not being called on the frontend.

Because that disconnect refers to the participant connection, not the agent connection. When you call delete_room_on_close, Agent session | LiveKit Documentation, the participant is being disconnected automatically since the room is deleted. If the participant is not going to disconnect themselves, DisconnectButton | React Components | LiveKit Documentation, then setting the value to true seems sensible for your use case

2. Guaranteed @session.on("close")

on_session_endis the best callback to use to upload chat history at the end of a conversation: Data hooks | LiveKit Documentation. Some users also upload conversation in real-time using conversation_item_added. I’m not sure why you are not seeing the session end callback in this report, Urgent: on_session_end callback not triggering in Agents SDK - #5 by Vishal_Maurya , for those specific rooms - perhaps check your agent log: Where to find your Agent Logs | LiveKit

3. Frontend onDisconnected callback after session.shutdown()

I believe you are conflating the participant disconnecting, Job lifecycle | LiveKit Documentation. Note the docs for that method “Other participants in the LiveKit room can continue.“

When using on_session_end, I noticed that in many cases the room was closing without triggering the callback. Due to this unreliability I switched to @session.on("close"), but for room RM_Srexx5fAFS4Q neither @session.on("close") nor on_session_end was called.

Could you please investigate this room and help me understand why both callbacks were skipped?

I have share the given link with iveKit support

Sure, can you please share you full agent log so we can see why it did not close the session. Sounds like some sort of agent logic issue.

Share Link :
Sign in | LiveKit Cloud

That link does not help. We need the agent logs. We do not have access to those.