From a7784f11fb13d66948670fc9375a5e0cc767ac62 Mon Sep 17 00:00:00 2001 From: LeonSGP43 Date: Sat, 11 Jul 2026 21:50:31 -0700 Subject: [PATCH] fix(codex): keep large-request ttfb watchdog active --- agent/chat_completion_helpers.py | 33 ++++++++++--------- tests/agent/test_codex_ttfb_watchdog.py | 43 +++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/agent/chat_completion_helpers.py b/agent/chat_completion_helpers.py index dbc946ce899a..2987784ca700 100644 --- a/agent/chat_completion_helpers.py +++ b/agent/chat_completion_helpers.py @@ -534,25 +534,28 @@ def interruptible_api_call(agent, api_kwargs: dict): and _ttfb_disable_above > 0 and _est_tokens_for_codex_watchdog >= _ttfb_disable_above ): - _ttfb_enabled = False - logger.info( - "Disabling openai-codex no-byte TTFB watchdog for large request " - "(context=~%s tokens >= %.0f). Waiting for backend response instead. " - "Set HERMES_CODEX_TTFB_STRICT=1 to force early reconnects.", - f"{_est_tokens_for_codex_watchdog:,}", - _ttfb_disable_above, - ) - else: - _ttfb_cap = _env_float("HERMES_CODEX_TTFB_MAX_SECONDS", 120.0) - if _ttfb_cap > 0 and _ttfb_timeout > _ttfb_cap: + _large_request_ttfb_timeout = _codex_idle_timeout_default + if _ttfb_timeout < _large_request_ttfb_timeout: logger.info( - "Capping openai-codex no-byte TTFB timeout from %.0fs to %.0fs " - "(context=~%s tokens). Set HERMES_CODEX_TTFB_MAX_SECONDS to tune.", + "Scaling openai-codex no-byte TTFB watchdog from %.0fs to %.0fs " + "for large request (context=~%s tokens >= %.0f). " + "Set HERMES_CODEX_TTFB_STRICT=1 to keep the smaller cutoff.", _ttfb_timeout, - _ttfb_cap, + _large_request_ttfb_timeout, f"{_est_tokens_for_codex_watchdog:,}", + _ttfb_disable_above, ) - _ttfb_timeout = _ttfb_cap + _ttfb_timeout = _large_request_ttfb_timeout + _ttfb_cap = _env_float("HERMES_CODEX_TTFB_MAX_SECONDS", 120.0) + if _ttfb_cap > 0 and _ttfb_timeout > _ttfb_cap: + logger.info( + "Capping openai-codex no-byte TTFB timeout from %.0fs to %.0fs " + "(context=~%s tokens). Set HERMES_CODEX_TTFB_MAX_SECONDS to tune.", + _ttfb_timeout, + _ttfb_cap, + f"{_est_tokens_for_codex_watchdog:,}", + ) + _ttfb_timeout = _ttfb_cap _codex_idle_enabled = _codex_watchdog_enabled _codex_idle_timeout = _env_float( diff --git a/tests/agent/test_codex_ttfb_watchdog.py b/tests/agent/test_codex_ttfb_watchdog.py index 983a4cbe4a03..283a6a8e9a8d 100644 --- a/tests/agent/test_codex_ttfb_watchdog.py +++ b/tests/agent/test_codex_ttfb_watchdog.py @@ -353,8 +353,8 @@ def test_ttfb_disabled_via_env_zero(tmp_path, monkeypatch): def test_large_codex_request_waits_instead_of_ttfb_reconnect(tmp_path, monkeypatch): """Large Codex inputs can legitimately take longer than the small-request - first-byte cutoff before the first SSE frame. Preserve the full input and - wait instead of killing/retrying at TTFB.""" + first-byte cutoff before the first SSE frame. Scale the TTFB timeout up + for those requests instead of killing/retrying at the small-request cutoff.""" from agent import chat_completion_helpers as h agent = _make_codex_agent(tmp_path, monkeypatch) @@ -386,6 +386,45 @@ def test_large_codex_request_waits_instead_of_ttfb_reconnect(tmp_path, monkeypat assert "codex_ttfb_kill" not in closes +def test_large_codex_request_can_still_ttfb_reconnect_when_capped(tmp_path, monkeypatch): + """Large Codex requests should keep a finite TTFB watchdog instead of + disabling it entirely. A low max cap should still force an early reconnect.""" + from agent import chat_completion_helpers as h + + agent = _make_codex_agent(tmp_path, monkeypatch) + monkeypatch.setenv("HERMES_CODEX_TTFB_TIMEOUT_SECONDS", "1") + monkeypatch.setenv("HERMES_CODEX_TTFB_MAX_SECONDS", "1") + + closes: list = [] + dummy_client = SimpleNamespace() + monkeypatch.setattr(agent, "_create_request_openai_client", lambda **k: dummy_client) + monkeypatch.setattr( + agent, "_abort_request_openai_client", lambda c, reason=None: closes.append(reason) + ) + monkeypatch.setattr( + agent, "_close_request_openai_client", lambda c, reason=None: closes.append(reason) + ) + + stop = {"flag": False} + + def fake_hang(api_kwargs, client=None, on_first_delta=None): + deadline = time.time() + 30 + while time.time() < deadline and not stop["flag"] and not agent._interrupt_requested: + time.sleep(0.02) + raise RuntimeError("connection closed") + + monkeypatch.setattr(agent, "_run_codex_stream", fake_hang) + + large_input = "x" * 44_000 # ~11k estimated tokens, above the large-request gate. + try: + with pytest.raises(TimeoutError) as excinfo: + h.interruptible_api_call(agent, {"model": "gpt-5.5", "input": large_input}) + assert "TTFB threshold: 1s" in str(excinfo.value) + assert "codex_ttfb_kill" in closes + finally: + stop["flag"] = True + + def test_large_codex_request_strict_ttfb_env_still_reconnects(tmp_path, monkeypatch): """Operators can force the old early-reconnect behavior for large inputs with HERMES_CODEX_TTFB_STRICT=1."""