openai/openai-python · error · RuntimeError
A single event handler cannot be shared between multiple str
Error message
A single event handler cannot be shared between multiple streams; You will need to construct a new event handler instance
What it means
This RuntimeError is thrown by AssistantEventHandler._init when the same handler instance is passed to a second streaming call. The handler stores per-stream state (the Stream reference, accumulated snapshots, iterators created in __init__), so entering a new stream with an already-bound handler would mix state from two runs. The library therefore forbids reuse after the handler has been entered via __enter__/__aenter__.
Source
Thrown at src/openai/lib/streaming/_assistants.py:63
def __init__(self) -> None:
self._current_event: AssistantStreamEvent | None = None
self._current_message_content_index: int | None = None
self._current_message_content: MessageContent | None = None
self._current_tool_call_index: int | None = None
self._current_tool_call: ToolCall | None = None
self.__current_run_step_id: str | None = None
self.__current_run: Run | None = None
self.__run_step_snapshots: dict[str, RunStep] = {}
self.__message_snapshots: dict[str, Message] = {}
self.__current_message_snapshot: Message | None = None
self.text_deltas = self.__text_deltas__()
self._iterator = self.__stream__()
self.__stream: Stream[AssistantStreamEvent] | None = None
def _init(self, stream: Stream[AssistantStreamEvent]) -> None:
if self.__stream:
raise RuntimeError(
"A single event handler cannot be shared between multiple streams; You will need to construct a new event handler instance"
)
self.__stream = stream
def __next__(self) -> AssistantStreamEvent:
return self._iterator.__next__()
def __iter__(self) -> Iterator[AssistantStreamEvent]:
for item in self._iterator:
yield item
@property
def current_event(self) -> AssistantStreamEvent | None:
return self._current_event
@property
def current_run(self) -> Run | None:View on GitHub (pinned to 9917c6e28e)
Solutions
- Construct a fresh AssistantEventHandler (or subclass) instance for every stream call
- If you loop over prompts, move handler construction inside the loop
- If you need shared logic, put it in the handler class (override on_event etc.) rather than sharing one instance
- Check that you are not accidentally passing the same handler to both a retry and the original call
Example fix
# before
handler = MyHandler()
for question in questions:
with client.beta.threads.runs.stream(thread_id=..., assistant_id=..., event_handler=handler) as stream:
stream.until_done()
# after
for question in questions:
handler = MyHandler() # new instance per stream
with client.beta.threads.runs.stream(thread_id=..., assistant_id=..., event_handler=handler) as stream:
stream.until_done() Defensive patterns
Strategy: validation
Validate before calling
def assert_fresh_handler(handler) -> None:
if getattr(handler, "_AssistantEventHandler__stream", None):
raise RuntimeError("handler already bound to a previous stream; construct a new one") Type guard
def is_unbound_handler(h: AssistantEventHandler) -> bool:
return getattr(h, "_AssistantEventHandler__stream", None) is None Try / catch
try:
with client.beta.threads.runs.stream(..., event_handler=handler) as s:
s.until_done()
except RuntimeError as e:
if "cannot be shared" in str(e):
handler = MyHandler() # fresh instance, then retry once Prevention
- Instantiate one handler per stream call, inside loops/request scopes
- Keep per-request state off shared handler objects
- Wrap handler construction near the stream call site
When it happens
Trigger: Calling client.beta.threads.runs.stream(...) (or create_and_stream) twice with the same AssistantEventHandler instance, e.g. in a loop: for q in questions: with client.beta.threads.runs.stream(thread_id=..., assistant_id=..., event_handler=handler). Any second stream entered with a handler whose _init already ran will raise.
Common situations: Retrying or looping over multiple user questions with one handler; sharing a module-level handler; partial-migration from the old Assistants streaming API where reuse appeared to work; forgetting that entering the handler in a with-block binds it permanently.
Related errors
- No final run object found
- No run steps found
- No messages found
- Stream has not been started yet
- Encountered a message delta with no previous snapshot
AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28).
Data as JSON: /api/errors/cf87d768af42c7e8.
Report an issue: GitHub.