From 906317c43d89867baea3ce19720b12da79d69ebf Mon Sep 17 00:00:00 2001 From: Henry Su Date: Fri, 21 Aug 2026 15:25:30 -0500 Subject: [PATCH 1/2] fix(voice): use monotonic STT event deadlines --- src/agents/voice/models/openai_stt.py | 6 ++++-- tests/voice/test_openai_stt.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/agents/voice/models/openai_stt.py b/src/agents/voice/models/openai_stt.py index cf504d8892..36c163060f 100644 --- a/src/agents/voice/models/openai_stt.py +++ b/src/agents/voice/models/openai_stt.py @@ -66,9 +66,11 @@ async def _wait_for_event( """ Wait for an event from event_queue whose type is in expected_types within the specified timeout. """ - start_time = time.time() + # Wall-clock adjustments can move a deadline forwards or backwards. Timeout + # accounting must use a monotonic clock instead. + start_time = time.monotonic() while True: - remaining = timeout - (time.time() - start_time) + remaining = timeout - (time.monotonic() - start_time) if remaining <= 0: raise TimeoutError(f"Timeout waiting for event(s): {expected_types}") evt = await asyncio.wait_for(event_queue.get(), timeout=remaining) diff --git a/tests/voice/test_openai_stt.py b/tests/voice/test_openai_stt.py index 50daf0c2ba..62597cb13d 100644 --- a/tests/voice/test_openai_stt.py +++ b/tests/voice/test_openai_stt.py @@ -32,6 +32,7 @@ ErrorSentinel, WebsocketDoneSentinel, _audio_buffer_to_base64, + _wait_for_event, ) from .pipeline_test_models import StreamedAudioInputFactory @@ -55,6 +56,16 @@ def create_mock_websocket(messages: list[str]) -> AsyncMock: return mock_ws +@pytest.mark.asyncio +async def test_wait_for_event_returns_matching_event() -> None: + queue: asyncio.Queue[dict[str, str]] = asyncio.Queue() + await queue.put({"type": "session.created"}) + + event = await _wait_for_event(queue, ["session.created"], timeout=1) + + assert event == {"type": "session.created"} + + def fake_time(increment: int): current = 1000 while True: @@ -579,8 +590,8 @@ async def test_timeout_waiting_for_created_event(monkeypatch): def fake_time_func(): return next(time_gen) - # Monkey-patch time.time with our fake_time_func - monkeypatch.setattr(time, "time", fake_time_func) + # Monkey-patch the monotonic clock used for event deadlines. + monkeypatch.setattr(time, "monotonic", fake_time_func) mock_ws = create_mock_websocket( [ From aad26e6b2e98e5a96c89d43947cf136b0993e7da Mon Sep 17 00:00:00 2001 From: Henry Su Date: Fri, 21 Aug 2026 23:34:38 -0500 Subject: [PATCH 2/2] test(voice): mock monotonic inactivity clock --- tests/voice/test_openai_stt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/voice/test_openai_stt.py b/tests/voice/test_openai_stt.py index 62597cb13d..6f26928242 100644 --- a/tests/voice/test_openai_stt.py +++ b/tests/voice/test_openai_stt.py @@ -769,12 +769,12 @@ async def test_inactivity_timeout(): ) # We'll artificially manipulate the "time" to simulate inactivity quickly. - # The code checks time.time() for inactivity over EVENT_INACTIVITY_TIMEOUT. + # The code checks time.monotonic() for inactivity over EVENT_INACTIVITY_TIMEOUT. # We'll increment the return_value manually. with ( patch("websockets.connect", return_value=mock_ws), patch( - "time.time", + "time.monotonic", side_effect=[ 1000.0, 1000.0 + EVENT_INACTIVITY_TIMEOUT + 1,