@@ -619,6 +619,31 @@ async def _await_run_and_cleanup() -> Any:
619619
620620 self .run_loop_task = asyncio .create_task (_await_run_and_cleanup ())
621621
622+ @property
623+ def run_loop_exception (self ) -> BaseException | None :
624+ """The exception raised by the background run loop, if any.
625+
626+ When the run loop fails before producing stream events (for example during early
627+ sandbox initialisation), the exception may not be re-raised through
628+ :meth:`stream_events`. This property gives callers a reliable way to check for
629+ silent failures after consuming the stream:
630+
631+ .. code-block:: python
632+
633+ result = Runner.run_streamed(agent, "hello")
634+ async for event in result.stream_events():
635+ pass
636+ if result.run_loop_exception:
637+ raise result.run_loop_exception
638+
639+ Returns ``None`` if the run loop completed without error, has not yet finished,
640+ or was cancelled.
641+ """
642+ task = self .run_loop_task
643+ if task is None or not task .done () or task .cancelled ():
644+ return None
645+ return task .exception ()
646+
622647 def cancel (self , mode : Literal ["immediate" , "after_turn" ] = "immediate" ) -> None :
623648 """Cancel the streaming run.
624649
@@ -725,6 +750,11 @@ async def stream_events(self) -> AsyncIterator[StreamEvent]:
725750 # Ensure main execution completes before cleanup to avoid race conditions
726751 # with session operations.
727752 await self ._await_task_safely (self .run_loop_task )
753+ # Re-check for exceptions now that the run loop has fully settled.
754+ # _await_task_safely swallows exceptions; without this call, a run-loop
755+ # failure that races past the sentinel (e.g. early sandbox failures) would
756+ # be silently lost instead of surfaced via _stored_exception.
757+ self ._check_errors ()
728758 # Safely terminate all background tasks after main execution has finished.
729759 self ._cleanup_tasks ()
730760
0 commit comments