From 9379ff6ca47c60f51d9f7fad7b22920aa6ad9558 Mon Sep 17 00:00:00 2001 From: Harrison Weinstock Date: Thu, 16 Apr 2026 18:07:10 +0000 Subject: [PATCH] fix(browser): set event loop on calling thread before nest_asyncio.apply() --- src/strands_tools/browser/browser.py | 2 ++ tests/browser/test_browser.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/strands_tools/browser/browser.py b/src/strands_tools/browser/browser.py index b00f1b8e..12a01a7b 100644 --- a/src/strands_tools/browser/browser.py +++ b/src/strands_tools/browser/browser.py @@ -938,6 +938,8 @@ def close(self, action: CloseAction) -> Dict[str, Any]: def _execute_async(self, action_coro) -> Any: # Apply nest_asyncio if not already applied if not self._nest_asyncio_applied: + # Ensure the event loop is correctly referenced on the active thread. + asyncio.set_event_loop(self._loop) nest_asyncio.apply() self._nest_asyncio_applied = True diff --git a/tests/browser/test_browser.py b/tests/browser/test_browser.py index ce3f9149..732dc5d6 100644 --- a/tests/browser/test_browser.py +++ b/tests/browser/test_browser.py @@ -2,6 +2,8 @@ Unit tests for the Browser base class using MockBrowser. """ +import asyncio +import concurrent.futures from unittest.mock import AsyncMock, Mock, patch from playwright.async_api import Browser as PlaywrightBrowser @@ -81,3 +83,21 @@ def test_browser_tool_integration(mock_async_playwright): tool_func = browser.browser assert hasattr(tool_func, "__name__") assert tool_func.__name__ == "browser" + + +def test_execute_async_works_from_foreign_thread(): + """_execute_async must work when called from a thread other than the one that ran __init__. + + The strands SDK dispatches sync tools via asyncio.to_thread, which runs the tool + on a worker thread. Without setting the event loop on that thread, + nest_asyncio.apply() fails with 'There is no current event loop in thread'. + """ + browser = MockBrowser() + + async def dummy(): + return "ok" + + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: + result = pool.submit(browser._execute_async, dummy()).result(timeout=5) + + assert result == "ok"