From 4a1840e6835058b6d7fc2457dc652d9cd8d61ce4 Mon Sep 17 00:00:00 2001 From: Zhekinmaksim Date: Sat, 9 May 2026 15:02:21 +0530 Subject: [PATCH] fix(async): replace get_event_loop() with get_running_loop() in async contexts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to PR #21293 (cli.py), which fixed the same anti-pattern. `asyncio.get_event_loop()` is documented as effectively "always returns the running loop when called from a coroutine" and emits DeprecationWarning/RuntimeWarning in some interpreter configurations. The Python docs explicitly recommend get_running_loop() inside coroutines. Replaces the remaining 9 call sites that are unconditionally inside async def bodies: - tools/browser_cdp_tool.py — _cdp_call() (4 sites): deadline + remaining computations inside the async websockets.connect context manager. - hermes_cli/web_server.py — get_status, _start_device_code_flow, submit_oauth_code (3 sites): all FastAPI async endpoints offloading blocking httpx / PKCE work to run_in_executor. - environments/agent_loop.py — HermesAgentLoop (1 site): tool dispatch inside the async rollout loop. - environments/benchmarks/terminalbench_2/terminalbench2_env.py — rollout_and_score_eval (1 site): test verification thread offload. All 9 sites are unconditionally inside async def bodies, so a running loop is guaranteed and no try/except RuntimeError fallback is needed (unlike the cli.py case in #21293, which ran from a background thread). Behavior is identical on supported Python versions; aligns the codebase with the post-#21293 idiom and avoids future warnings as the deprecation hardens. Salvaged from PR #21930 by @Zhekinmaksim onto current main (the original branch was 109 commits behind and carried unintended stale-branch reverts of unrelated landed changes — _tail_lines encoding=utf-8 and the Windows PTY bridge guard). Only the 9 swaps from the PR's intended scope are applied here. --- environments/agent_loop.py | 2 +- .../benchmarks/terminalbench_2/terminalbench2_env.py | 2 +- hermes_cli/web_server.py | 6 +++--- tools/browser_cdp_tool.py | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/environments/agent_loop.py b/environments/agent_loop.py index 891ce42f448..7ca3a0f6ddb 100644 --- a/environments/agent_loop.py +++ b/environments/agent_loop.py @@ -403,7 +403,7 @@ class HermesAgentLoop: # Run tool calls in a thread pool so backends that # use asyncio.run() internally (modal, docker, daytona) get # a clean event loop instead of deadlocking. - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() # Capture current tool_name/args for the lambda _tn, _ta, _tid = tool_name, args, self.task_id tool_result = await loop.run_in_executor( diff --git a/environments/benchmarks/terminalbench_2/terminalbench2_env.py b/environments/benchmarks/terminalbench_2/terminalbench2_env.py index db6f6f58d73..0e88ac347fa 100644 --- a/environments/benchmarks/terminalbench_2/terminalbench2_env.py +++ b/environments/benchmarks/terminalbench_2/terminalbench2_env.py @@ -575,7 +575,7 @@ class TerminalBench2EvalEnv(HermesAgentBaseEnv): # other tasks, tqdm updates, and timeout timers). ctx = ToolContext(task_id) try: - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() reward = await loop.run_in_executor( None, # default thread pool self._run_tests, eval_item, ctx, task_name, diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index c14cf762a40..c4647787209 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -533,7 +533,7 @@ async def get_status(): remote_health_body: dict | None = None if not gateway_running and _GATEWAY_HEALTH_URL: - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() alive, remote_health_body = await loop.run_in_executor( None, _probe_gateway_health ) @@ -1845,7 +1845,7 @@ async def _start_device_code_flow(provider_id: str) -> Dict[str, Any]: client_id=client_id, scope=scope, ) - device_data = await asyncio.get_event_loop().run_in_executor(None, _do_nous_device_request) + device_data = await asyncio.get_running_loop().run_in_executor(None, _do_nous_device_request) sid, sess = _new_oauth_session("nous", "device_code") sess["device_code"] = str(device_data["device_code"]) sess["interval"] = int(device_data["interval"]) @@ -2134,7 +2134,7 @@ async def submit_oauth_code(provider_id: str, body: OAuthSubmitBody, request: Re """Submit the auth code for PKCE flows. Token-protected.""" _require_token(request) if provider_id == "anthropic": - return await asyncio.get_event_loop().run_in_executor( + return await asyncio.get_running_loop().run_in_executor( None, _submit_anthropic_pkce, body.session_id, body.code, ) raise HTTPException(status_code=400, detail=f"submit not supported for {provider_id}") diff --git a/tools/browser_cdp_tool.py b/tools/browser_cdp_tool.py index d43d200b4a6..8e829556a57 100644 --- a/tools/browser_cdp_tool.py +++ b/tools/browser_cdp_tool.py @@ -132,9 +132,9 @@ async def _cdp_call( } ) ) - deadline = asyncio.get_event_loop().time() + timeout + deadline = asyncio.get_running_loop().time() + timeout while True: - remaining = deadline - asyncio.get_event_loop().time() + remaining = deadline - asyncio.get_running_loop().time() if remaining <= 0: raise TimeoutError( f"Timed out attaching to target {target_id}" @@ -166,9 +166,9 @@ async def _cdp_call( req["sessionId"] = session_id await ws.send(json.dumps(req)) - deadline = asyncio.get_event_loop().time() + timeout + deadline = asyncio.get_running_loop().time() + timeout while True: - remaining = deadline - asyncio.get_event_loop().time() + remaining = deadline - asyncio.get_running_loop().time() if remaining <= 0: raise TimeoutError( f"Timed out waiting for response to {method}"