Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ def _close_client_on_timeout() -> None:

def _check_cancelled() -> None:
if deadline is not None and time.monotonic() >= deadline:
timed_out.set()
_close_client_on_timeout()
raise TimeoutError(_timeout_message())
try:
from tools.interrupt import is_interrupted
Expand Down
64 changes: 64 additions & 0 deletions tests/agent/test_auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,70 @@ def close(self):
with _client_cache_lock:
_client_cache.clear()

def test_codex_loop_detected_timeout_evicts_cached_wrapper(self):
"""Loop-detected Codex timeouts must close and evict cached clients."""
from agent.auxiliary_client import (
_client_cache, _client_cache_lock,
_CodexCompletionsAdapter, CodexAuxiliaryClient,
)

class NoopTimer:
daemon = False

def __init__(self, *args, **kwargs):
pass

def start(self):
pass

def cancel(self):
pass

class DeadlineWinsStream:
def __enter__(self):
return self

def __exit__(self, exc_type, exc, tb):
return False

def __iter__(self):
time.sleep(0.02)
yield SimpleNamespace(type="response.in_progress")

def get_final_response(self): # pragma: no cover - timeout fires first
return SimpleNamespace(output=[], usage=None)

closed = {"flag": False}

class FakeClient:
def __init__(self):
self.responses = SimpleNamespace(stream=lambda **k: DeadlineWinsStream())
self.api_key = "k"
self.base_url = "https://chatgpt.com/backend-api/codex"

def close(self):
closed["flag"] = True

fake_real = FakeClient()
wrapper = CodexAuxiliaryClient(fake_real, "gpt-5.5")
cache_key = ("openai-codex", False, None, None, None)
with _client_cache_lock:
_client_cache.clear()
_client_cache[cache_key] = (wrapper, "gpt-5.5", None)
try:
adapter = _CodexCompletionsAdapter(fake_real, "gpt-5.5")
with patch("agent.auxiliary_client.threading.Timer", NoopTimer):
with pytest.raises(TimeoutError):
adapter.create(
messages=[{"role": "user", "content": "x"}],
timeout=0.001,
)
assert closed["flag"] is True
assert cache_key not in _client_cache
finally:
with _client_cache_lock:
_client_cache.clear()

def test_call_llm_evicts_on_connection_error_with_explicit_provider(self):
"""Connection error on an explicit provider must drop the cached client.

Expand Down