openai / openai/openai-agents-python
Realtime: a failing model.close() leaves every waiting event iterator blocked forever
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 29.6k
- Forks
- 4.8k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 123
Description
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:
mainat 9f6f6b10 (also present in v0.22.2) - Python version: 3.12.13
- Operating system: macOS 15
- Model and model provider: any
RealtimeModelwhoseclose()raises; reproduced withScriptedRealtimeModel(close_error=...)fromagents.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.
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in RealtimeSession.close/_cleanup and follow aiter plus _wake_event_iterators; issue #3284 describes the successful-close behavior to compare. Reproduce with ScriptedRealtimeModel(close_error=...), then run the regression covering test_concurrent_close_callers_share_failure_and_retry. Done means close() still raises and remains retryable while parked iterators drain queued events and terminate.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- audio-video-rtc
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100