{"record":{"id":"0e1e5ecb90d34613","repo":"docling-project/docling","slug":"timed-out-waiting-for-task-task-id-to-emit-statu","errorCode":null,"errorMessage":"Timed out waiting for task {task_id} to emit status updates.","messagePattern":"Timed out waiting for task (.+?) to emit status updates\\.","errorType":"exception","errorClass":"TaskTimeoutError","httpStatus":null,"severity":"warning","filePath":"docling/service_client/watchers.py","lineNumber":142,"sourceCode":"\n            # Keep a minimum client-side poll cadence when server-side wait is ignored.\n            sleep_for = _poll_sleep_duration(\n                poll_started=poll_started,\n                poll_interval=self._poll_client_interval,\n                deadline=deadline,\n            )\n            if sleep_for > 0:\n                time.sleep(sleep_for)\n\n    def wait_for_terminal(\n        self, task_id: str, timeout: float | None = None\n    ) -> TaskStatusResponse:\n        final_status: TaskStatusResponse | None = None\n        for update in self.iter_updates(task_id=task_id, timeout=timeout):\n            final_status = update\n\n        if final_status is None:\n            raise TaskTimeoutError(\n                f\"Timed out waiting for task {task_id} to emit status updates.\"\n            )\n        return final_status\n\n\nclass WebSocketWatcher:\n    \"\"\"Status watcher using `WS /v1/status/ws/{task_id}` with poll fallback.\"\"\"\n\n    def __init__(\n        self,\n        ws_url_for_task: Callable[[str], str],\n        poll_fallback: PollingWatcher | None,\n        fallback_to_poll: bool,\n        connect_timeout: float,\n        default_timeout: float,\n        additional_headers: dict[str, str] | None = None,\n    ) -> None:\n        self._ws_url_for_task = ws_url_for_task","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/service_client/watchers.py#L124-L160","documentation":"TaskTimeoutError raised by PollingWatcher.wait_for_terminal when iter_updates yielded zero updates — the final_status is still None. This happens when the very first poll consumes/exceeds the whole deadline (e.g. server-side long-poll blocks until timeout) so the generator never yields before the deadline check fires.","triggerScenarios":"wait_for_terminal with a timeout smaller than one server long-poll cycle (poll_server_wait), so the first _poll_status consumes the entire budget and the loop exits with no yielded update.","commonSituations":"Passing a very small explicit timeout (a few seconds) against a server configured with long poll waits; tight deadline arithmetic where remaining <= 0 right after the first poll returns.","solutions":["Use a timeout comfortably larger than poll_server_wait (one full poll cycle plus margin)","Omit the timeout to use the client default, which is sized for normal polling","If it recurs with sane timeouts, check whether the service's long-poll endpoint is hanging"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"def is_task_timeout(exc: BaseException) -> bool:\n    return isinstance(exc, TaskTimeoutError)","tryCatchPattern":"from docling.service_client.exceptions import TaskTimeoutError\n\ntry:\n    status = watcher.wait_for_terminal(task_id)\nexcept TaskTimeoutError as exc:\n    if 'emit status updates' in str(exc):\n        status = watcher.wait_for_terminal(task_id, timeout=timeout_s * 3)","preventionTips":["Never pass a timeout smaller than one poll cycle","Use the default timeout unless you know the poll cadence"],"tags":["timeout","polling","watchers"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}