0e13fa98ec
A provider whose backend keeps the response open without finishing (hanging HTTP server, stuck SDK call) stalled the web_extract tool call — and with a sync provider, the borrowed thread — indefinitely. The dispatch in tools/web_tools_extract._dispatch_extract now runs under asyncio.wait_for with web.extract_timeout (config.yaml, default 120s; 0 disables). On timeout the tool returns structured per-URL error entries, and the one-shot keyless rescue still gets its chance when eligible. Salvaged from PR #57180 by @liuhao1024 (base predated the web_tools decomposition; re-applied at the _dispatch_extract seam, env-var timeout replaced with the web.* config section per the .env-is-for-secrets rule, and the timeout path made rescue-aware). Inspired by Claude Code 2.1.268: "Fixed WebFetch hanging indefinitely on a server that keeps the response open without finishing; a fetch now fails after 300 seconds." Fixes #57155 Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>