From 6bd2950b9937d4e693692221e96d2c262671b53f Mon Sep 17 00:00:00 2001 From: hoaresky Date: Sun, 5 Apr 2026 09:12:49 +0800 Subject: [PATCH] Fix: add asyncio timeout guard for DuckDuckGo search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DDGS's internal `timeout=10` relies on `requests` read-timeout semantics, which only measure the gap between bytes — not total wall-clock time. When the underlying HTTP connection enters CLOSE-WAIT or the server dribbles data slowly, this timeout never fires, causing `ddgs.text` to hang indefinitely via `asyncio.to_thread`. Since `asyncio.to_thread` cannot cancel the underlying OS thread, the agent's session lock is never released, blocking all subsequent messages on the same session (observed: 8+ hours of unresponsiveness). Fix: - Add `timeout` field to `WebSearchConfig` (default: 30s, configurable via config.json or NANOBOT_TOOLS__WEB__SEARCH__TIMEOUT env var) - Wrap `asyncio.to_thread` with `asyncio.wait_for` to enforce a hard wall-clock deadline Closes #2804 Co-Authored-By: Claude Opus 4.6 --- nanobot/agent/tools/web.py | 5 ++++- nanobot/config/schema.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nanobot/agent/tools/web.py b/nanobot/agent/tools/web.py index b8aeab47b..a6d7be983 100644 --- a/nanobot/agent/tools/web.py +++ b/nanobot/agent/tools/web.py @@ -207,7 +207,10 @@ class WebSearchTool(Tool): from ddgs import DDGS ddgs = DDGS(timeout=10) - raw = await asyncio.to_thread(ddgs.text, query, max_results=n) + raw = await asyncio.wait_for( + asyncio.to_thread(ddgs.text, query, max_results=n), + timeout=self.config.timeout, + ) if not raw: return f"No results for: {query}" items = [ diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index 0b5d6a817..47e35070c 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -155,6 +155,7 @@ class WebSearchConfig(Base): api_key: str = "" base_url: str = "" # SearXNG base URL max_results: int = 5 + timeout: int = 30 # Wall-clock timeout (seconds) for search operations class WebToolsConfig(Base):