{"record":{"id":"94309853ae0ba06e","repo":"NousResearch/hermes-agent","slug":"non-streaming-api-call-timed-out-after-int-time-t","errorCode":null,"errorMessage":"Non-streaming API call timed out after {int(time.time() - call_start)}s with no response (threshold: {int(stale_timeout)}s)","messagePattern":"Non-streaming API call timed out after (.+?)s with no response \\(threshold: (.+?)s\\)","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"agent/chat_completion_helpers.py","lineNumber":814,"sourceCode":"    # Only a clean return may report the reuse reason (request_complete):\n    # after an error or interrupt the wire client is really closed so the\n    # retry builds a fresh pool (see _REQUEST_CLIENT_REUSE_REASONS).\n    succeeded = False\n    try:\n        response = _dispatch_nonstreaming_api_request(\n            agent, api_kwargs, make_client=_make_client\n        )\n    except Exception:\n        if getattr(agent, \"_interrupt_requested\", False):\n            raise InterruptedError(\"Agent interrupted during API call\") from None\n        with request_client_lock:\n            was_stale = request_state[\"stale\"]\n        if was_stale:\n            # The transport error is the expected consequence of our own\n            # abort. Raise a retryable TimeoutError (never InterruptedError,\n            # which the outer loop treats as \"the user wants to stop\") so the\n            # retry loop reconnects on a fresh pool.\n            raise TimeoutError(\n                f\"Non-streaming API call timed out after \"\n                f\"{int(time.time() - call_start)}s with no response \"\n                f\"(threshold: {int(stale_timeout)}s)\"\n            ) from None\n        raise\n    else:\n        if getattr(agent, \"_interrupt_requested\", False):\n            raise InterruptedError(\"Agent interrupted during API call\")\n        # Close the race window against a timer firing between response\n        # arrival and this unwind: marking ``done`` under the lock makes any\n        # later timer callback a no-op, so the reset below cannot be\n        # overwritten by a stray bump after a successful call. A timer that\n        # already won the lock left ``stale`` set — the request still\n        # completed, so return the response (the streak reset undoes the\n        # bump; the poisoned client is discarded by the finally).\n        with request_client_lock:\n            request_state[\"done\"] = True\n        _reset_stale_streak(agent)","sourceCodeStart":796,"sourceCodeEnd":832,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/chat_completion_helpers.py#L796-L832","documentation":"The non-streaming API call raised a transport exception and the watchdog had already flagged the request stale; Hermes deliberately converts this into a retryable TimeoutError (never InterruptedError, which would read as 'user wants to stop') so the outer retry loop reconnects on a fresh pool. Elapsed time and threshold are included for diagnosis.","triggerScenarios":"A non-streaming completion received no response within stale_timeout; the abort timer fired, killed the request, the transport then raised, and the stale flag was set at agent/chat_completion_helpers.py:814.","commonSituations":"Slow provider exceeding the stale threshold; oversized prompt causing long time-to-first-byte on non-streaming responses; network black-holing; provider under heavy load.","solutions":["Retry — this error is intentionally retryable and the loop uses a fresh connection pool.","If recurrent, lower prompt size or switch to a streaming call (time-to-first-token arrives earlier).","Increase the stale timeout for legitimately slow setups (very large contexts, local models on weak hardware).","Check provider status / network path if every call times out."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if estimated_time_to_first_byte(provider, prompt_tokens) > stale_timeout:\n    trim_prompt_or_switch_to_streaming()","typeGuard":null,"tryCatchPattern":"attempts = 0\nwhile attempts < 3:\n    try:\n        return non_streaming_call(...)\n    except TimeoutError as e:\n        if \"no response\" in str(e) and \"threshold\" in str(e):\n            attempts += 1\n            backoff(attempts)\n            continue\n        raise","preventionTips":["Use streaming for large prompts — tokens arrive before the stale threshold.","Tune stale timeouts for slow local models instead of relying on retries.","Treat recurring stale timeouts as an endpoint health signal, not noise."],"tags":["non-streaming","timeout","retry","stale"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}