openai/openai-python · error · RuntimeError

Stream has not been started yet

Error message

Stream has not been started yet

What it means

Thrown by AssistantEventHandler.__stream__ when the internal generator is advanced before the handler has been bound to a real Stream via _init. __init__ eagerly creates self._iterator = self.__stream__(), but the generator body only runs on first next(); if iteration happens outside a with client...stream(...) block (which calls __enter__ -> _init), self.__stream is still None.

Source

Thrown at src/openai/lib/streaming/_assistants.py:406

            if self._current_tool_call:
                self.on_tool_call_done(self._current_tool_call)

            self.on_run_step_done(event.data)
            self.__current_run_step_id = None
        elif event.event == "thread.created" or event.event == "thread.message.in_progress" or event.event == "error":
            # currently no special handling
            ...
        else:
            # we only want to error at build-time
            if TYPE_CHECKING:  # type: ignore[unreachable]
                assert_never(event)

        self._current_event = None

    def __stream__(self) -> Iterator[AssistantStreamEvent]:
        stream = self.__stream
        if not stream:
            raise RuntimeError("Stream has not been started yet")

        try:
            for event in stream:
                self._emit_sse_event(event)

                yield event
        except _timeout_exceptions() as exc:
            self.on_timeout()
            self.on_exception(exc)
            raise
        except Exception as exc:
            self.on_exception(exc)
            raise
        finally:
            self.on_end()


AssistantEventHandlerT = TypeVar("AssistantEventHandlerT", bound=AssistantEventHandler)

View on GitHub (pinned to 9917c6e28e)

Solutions

  1. Always consume events/deltas inside the with (or async with) block returned by client.beta.threads.runs.stream / create_and_stream
  2. Do not call next() on handler or its text_deltas iterator before entering the context
  3. If you need the deltas, iterate stream.text_deltas on the stream object returned by the context manager, not on a bare handler

Example fix

# before
handler = MyHandler()
deltas = list(handler.text_deltas)  # stream not started

# after
with client.beta.threads.runs.stream(..., event_handler=handler) as stream:
    for delta in stream.text_deltas:
        print(delta, end="")
Defensive patterns

Strategy: validation

Validate before calling

def ensure_started(handler) -> None:
    if getattr(handler, "_AssistantEventHandler__stream", None) is None:
        raise RuntimeError("enter the stream context before iterating")

Type guard

def handler_is_live(h) -> bool:
    return getattr(h, "_AssistantEventHandler__stream", None) is not None

Try / catch

try:
    for delta in handler.text_deltas:
        ...
except RuntimeError as e:
    if "not been started" in str(e):
        # use the context-managed stream instead
        ...

Prevention

When it happens

Trigger: Manually iterating handler.text_deltas or the handler itself without entering the stream context: next(handler) or for delta in handler.text_deltas before with client.beta.threads.runs.stream(..., event_handler=handler) has run. Also instantiating the handler and calling until_done() directly.

Common situations: Calling list(handler.text_deltas) to pre-collect output; refactoring away the with-block; misunderstandings about when the stream starts; sharing handler-created iterators across contexts.

Related errors


AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28). Data as JSON: /api/errors/c8d0fd5368196af9. Report an issue: GitHub.