Skip to content

fix: #2929 surface run-loop exceptions after stream_events() completes#2931

Merged
seratch merged 1 commit into
openai:mainfrom
nileshpatil6:fix/surface-run-loop-exception-2929
Apr 18, 2026
Merged

fix: #2929 surface run-loop exceptions after stream_events() completes#2931
seratch merged 1 commit into
openai:mainfrom
nileshpatil6:fix/surface-run-loop-exception-2929

Conversation

@nileshpatil6
Copy link
Copy Markdown
Contributor

Summary

Closes #2929.

When Runner.run_streamed() is used and the background run loop raises an exception early (e.g. during sandbox initialisation, before any stream event is produced), the failure could be silently swallowed — leaving callers with a result that looks clean (final_output=None, new_items=[]) but is actually broken.

This PR makes two minimal, additive changes to RunResultStreaming:

1. run_loop_exception property

A new read-only property that directly exposes the background task's exception after the stream ends, without requiring callers to reach into internal task state:

result = Runner.run_streamed(agent, "hello")
async for event in result.stream_events():
    pass

if result.run_loop_exception:
    raise result.run_loop_exception

Returns None if the run completed without error, has not yet finished, or was cancelled.

2. _check_errors() call in the stream_events() finally block

_await_task_safely() intentionally swallows all exceptions (they're supposed to be surfaced via _stored_exception). But _check_errors() was not called after _await_task_safely(run_loop_task) in the non-cancelled finally path. This meant a run-loop failure that raced past the sentinel was not transferred into _stored_exception, so the final if self._stored_exception: raise never fired.

Adding self._check_errors() after await self._await_task_safely(self.run_loop_task) closes this gap.

Changes

  • src/agents/result.py — adds run_loop_exception property + _check_errors() call in stream_events() finally block
  • tests/test_cancel_streaming.py — adds two tests:
    • test_run_loop_exception_property_is_none_on_success — property is None on a clean run
    • test_run_loop_exception_surfaced_after_stream — exception is both raised through stream_events() and accessible via the property

Test plan

  • uv run pytest tests/test_cancel_streaming.py -v — 11 passed (9 existing + 2 new)
  • uv run pyright src/agents/result.py — 0 errors

Two changes to RunResultStreaming:

1. Add a `run_loop_exception` property that returns the background
   run-loop task's exception (or None) after the stream ends.
   Users no longer need to reach into `result.run_loop_task.exception()`
   to detect silent failures (e.g. early sandbox init errors).

2. Call `_check_errors()` in the non-cancelled `finally` path of
   `stream_events()` immediately after `_await_task_safely(run_loop_task)`.
   Previously, exceptions that raced past the queue sentinel were silently
   swallowed because `_await_task_safely` catches all exceptions and no
   subsequent check re-examined the task state.

Closes openai#2929
@github-actions github-actions Bot added bug Something isn't working feature:core labels Apr 17, 2026
@seratch seratch added this to the 0.14.x milestone Apr 18, 2026
@seratch seratch changed the title fix: surface run-loop exceptions after stream_events() completes fix: #2929 surface run-loop exceptions after stream_events() completes Apr 18, 2026
@seratch seratch merged commit 61443ca into openai:main Apr 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working feature:core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface run-loop exceptions in stream_events() for early failures

2 participants