Realtime: a failing model.close() leaves every waiting event iterator blocked forever

Author: kosesenaCreated Sep 17, 2026Updated Sep 17, 2026

Please read this first

  • Have you read the docs? Yes.
  • Have you searched for related issues? Yes. #3284 fixed close() leaving event iterators blocked on the successful close path; this is the failure path of the same contract.

Describe the bug

When RealtimeSession.close() runs and the model's close() raises, _cleanup has already removed the session as the model's listener and set _closing, but it never reaches the lines that set _closed and call _wake_event_iterators(). A consumer parked in async for event in session then waits forever: no event can reach the queue any more (_put_event refuses events while closing and the listener is gone), and __aiter__ only terminates on _closed, on _model_stream_ended, or on a wake-up sentinel that was never sent.

The retry design is deliberate and stays intact (_closed stays False so a second close() closes the model again, as test_concurrent_close_callers_share_failure_and_retry pins). But the task that called close() is usually not the task that iterates, and async with session: surfaces the close error from __aexit__ with nothing telling the application that its consumer task is now stuck. The lifecycle reference lists "wake or terminate every waiter on close" as the required release for event iterators.

Debug information

  • Agents SDK version: main at 9f6f6b10 (also present in v0.22.2)
  • Python version: 3.12.13
  • Operating system: macOS 15
  • Model and model provider: any RealtimeModel whose close() raises; reproduced with ScriptedRealtimeModel(close_error=...) from agents.realtime.testing. OpenAIRealtimeWebSocketModel.close() re-raises WebSocket close failures.
  • Does the issue reproduce with the latest Agents SDK release? Yes
  • Does the issue occur consistently or intermittently? Consistently

Repro steps

Self-contained, no network.

python
import asyncio

from agents.realtime.agent import RealtimeAgent
from agents.realtime.session import RealtimeSession
from agents.realtime.testing import ScriptedRealtimeModel


async def main():
    model = ScriptedRealtimeModel(close_error=RuntimeError("close failed"), strict=False)
    session = RealtimeSession(model, RealtimeAgent(name="agent"), None)
    await session.enter()

    async def consume():
        async for _ in session:
            pass

    consumer = asyncio.create_task(consume())
    await asyncio.sleep(0.05)  # Let the consumer park on the event queue.

    try:
        await session.close()
    except RuntimeError as error:
        print("close() raised:", error)

    try:
        await asyncio.wait_for(consumer, timeout=1)
        print("consumer ended")
    except asyncio.TimeoutError:
        print("consumer still blocked after close() raised")
        consumer.cancel()


asyncio.run(main())

Output on main:

close() raised: close failed
consumer still blocked after close() raised

Expected behavior

close() raised: close failed
consumer ended

close() still raises and the session stays retryable, but the parked consumer is released: waiting iterators are woken when the model close fails, and an iterator that resumes afterwards drains whatever was queued before the close and then ends instead of waiting for events that can no longer arrive. I have a fix with regression tests ready and will open a PR referencing this issue.

Source: openai/openai-agents-python