{"record":{"id":"e7e808e99a4cc823","repo":"NousResearch/hermes-agent","slug":"non-streaming-api-call-timed-out-before-request-di","errorCode":null,"errorMessage":"Non-streaming API call timed out before request dispatch (threshold: {int(stale_timeout)}s)","messagePattern":"Non-streaming API call timed out before request dispatch \\(threshold: (.+?)s\\)","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"agent/chat_completion_helpers.py","lineNumber":744,"sourceCode":"                # (registration race): the abort found no sockets to kill, so\n                # handing this client to dispatch would open a brand-new\n                # socket after the only watchdog already fired. Abort it here\n                # and fail the call instead. (The residual window — timer\n                # firing between this registration and httpx opening its\n                # socket — is accepted: it is ms-scale against a >=90s budget\n                # and self-bounds at the OS connect-retry cap.)\n                stale_before_dispatch = True\n                try:\n                    agent._abort_request_openai_client(\n                        client, reason=\"stale_call_kill\"\n                    )\n                except Exception:\n                    logger.debug(\n                        \"Inline abort after late client registration failed\",\n                        exc_info=True,\n                    )\n        if stale_before_dispatch:\n            raise TimeoutError(\n                \"Non-streaming API call timed out before request dispatch \"\n                f\"(threshold: {int(stale_timeout)}s)\"\n            )\n        agent._active_request_abort = _abort_active_request\n        return client\n\n    def _activity_heartbeat() -> None:\n        # Do not put the API call itself on another worker thread — that is\n        # the nested-pool deadlock this path exists to avoid (#60203). This\n        # ticker only refreshes the activity clock.\n        while not activity_hb_stop.wait(_DIRECT_API_ACTIVITY_HEARTBEAT_SECONDS):\n            try:\n                agent._touch_activity(\"waiting for non-streaming API response\")\n            except Exception:\n                pass\n\n    activity_hb = threading.Thread(\n        target=_activity_heartbeat,","sourceCodeStart":726,"sourceCodeEnd":762,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/chat_completion_helpers.py#L726-L762","documentation":"On the non-streaming API path, a stale watchdog detected that the call exceeded the stale threshold before the request was even dispatched (client registration/creation stalled, e.g. a wedged connection pool). The client was aborted via _abort_request_openai_client and the code raises TimeoutError pre-dispatch so the retry loop can rebuild on a fresh pool instead of stacking onto the dead one.","triggerScenarios":"The make_client/registration path itself blocks past the stale_timeout — connect-retry caps, exhausted connection pool, or DNS stall — before any bytes are sent (agent/chat_completion_helpers.py:744).","commonSituations":"Provider endpoint half-down (TCP accepts, TLS hangs); saturated local connection pool from earlier stale calls; DNS issues; localhost model server that died with sockets lingering.","solutions":["Let the retry loop run — the pre-dispatch abort is designed to reconnect on a fresh pool; persistent recurrence means the endpoint is down.","Verify the base_url endpoint health (curl -m 10).","Restart or switch the model server/provider.","For local endpoints, confirm the server process is alive and not out of file descriptors."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import socket, urllib.parse\ndef endpoint_dispatchable(base_url, timeout=10) -> bool:\n    u = urllib.parse.urlparse(base_url)\n    try:\n        with socket.create_connection((u.hostname, u.port or (443 if u.scheme == \"https\" else 80)), timeout=timeout):\n            return True\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"for attempt in range(max_retries):\n    try:\n        return non_streaming_call(...)\n    except TimeoutError as e:\n        if \"before request dispatch\" in str(e):\n            continue  # fresh pool on next iteration by design\n        raise","preventionTips":["Health-check endpoints before long runs.","Keep connection pools bounded so one wedged pool cannot stall dispatch.","For local model servers, monitor process liveness and file-descriptor exhaustion."],"tags":["non-streaming","timeout","stale","connection-pool"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}