From 33ed72a0f59062c7515df2214cdb67b9c0d27b67 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Fri, 24 Jul 2026 12:18:57 -0500 Subject: [PATCH] fix: chain asyncio.TimeoutError cause in _call_with_timeout Both coding_loop.py and dag_executor.py define _call_with_timeout and catch asyncio.TimeoutError, but they raised a plain TimeoutError without chaining the original exception. Preserve the cause so callers can trace the timeout back to its source. Adds a parametrized unit test that asserts TimeoutError.__cause__ is the original asyncio.TimeoutError for both implementations. --- swe_af/execution/coding_loop.py | 4 ++-- swe_af/execution/dag_executor.py | 4 ++-- tests/test_call_with_timeout.py | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/test_call_with_timeout.py diff --git a/swe_af/execution/coding_loop.py b/swe_af/execution/coding_loop.py index 9790e9e3..394d5e20 100644 --- a/swe_af/execution/coding_loop.py +++ b/swe_af/execution/coding_loop.py @@ -35,8 +35,8 @@ async def _call_with_timeout(coro, timeout: int = 2700, label: str = ""): """Wrap a coroutine with asyncio.wait_for timeout.""" try: return await asyncio.wait_for(coro, timeout=timeout) - except asyncio.TimeoutError: - raise TimeoutError(f"Agent call '{label}' timed out after {timeout}s") + except asyncio.TimeoutError as exc: + raise TimeoutError(f"Agent call '{label}' timed out after {timeout}s") from exc # --------------------------------------------------------------------------- diff --git a/swe_af/execution/dag_executor.py b/swe_af/execution/dag_executor.py index 212000b1..46480cfc 100644 --- a/swe_af/execution/dag_executor.py +++ b/swe_af/execution/dag_executor.py @@ -42,10 +42,10 @@ async def _call_with_timeout(coro, timeout: int = 2700, label: str = ""): """ try: return await asyncio.wait_for(coro, timeout=timeout) - except asyncio.TimeoutError: + except asyncio.TimeoutError as exc: raise TimeoutError( f"Agent call '{label}' timed out after {timeout}s" - ) + ) from exc # --------------------------------------------------------------------------- diff --git a/tests/test_call_with_timeout.py b/tests/test_call_with_timeout.py new file mode 100644 index 00000000..1bc1d47c --- /dev/null +++ b/tests/test_call_with_timeout.py @@ -0,0 +1,23 @@ +"""Unit tests for the timeout wrappers used by the execution engine.""" + +from __future__ import annotations + +import asyncio + +import pytest + +from swe_af.execution.coding_loop import _call_with_timeout as coding_loop_timeout +from swe_af.execution.dag_executor import _call_with_timeout as dag_executor_timeout + + +@pytest.mark.parametrize("fn", [coding_loop_timeout, dag_executor_timeout]) +async def test_call_with_timeout_chains_original_exception(fn): + """TimeoutError must carry the original asyncio.TimeoutError as its cause.""" + + async def slow(): + raise asyncio.TimeoutError("simulated") + + with pytest.raises(TimeoutError) as exc_info: + await fn(slow(), timeout=1, label="test") + + assert isinstance(exc_info.value.__cause__, asyncio.TimeoutError)