fix(async): replace get_event_loop() with get_running_loop() in async contexts

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.
This commit is contained in:
Zhekinmaksim 2026-05-09 15:02:21 +05:30 committed by kshitij
parent b7d8e280e8
commit 4a1840e683
4 changed files with 9 additions and 9 deletions

View file

@ -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(

View file

@ -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,

View file

@ -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}")

View file

@ -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}"