docling-project/docling · warning · TaskTimeoutError
Timed out waiting for task {task_id} to emit status updates.
Error message
Timed out waiting for task {task_id} to emit status updates. What it means
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.
Source
Thrown at docling/service_client/watchers.py:142
# Keep a minimum client-side poll cadence when server-side wait is ignored.
sleep_for = _poll_sleep_duration(
poll_started=poll_started,
poll_interval=self._poll_client_interval,
deadline=deadline,
)
if sleep_for > 0:
time.sleep(sleep_for)
def wait_for_terminal(
self, task_id: str, timeout: float | None = None
) -> TaskStatusResponse:
final_status: TaskStatusResponse | None = None
for update in self.iter_updates(task_id=task_id, timeout=timeout):
final_status = update
if final_status is None:
raise TaskTimeoutError(
f"Timed out waiting for task {task_id} to emit status updates."
)
return final_status
class WebSocketWatcher:
"""Status watcher using `WS /v1/status/ws/{task_id}` with poll fallback."""
def __init__(
self,
ws_url_for_task: Callable[[str], str],
poll_fallback: PollingWatcher | None,
fallback_to_poll: bool,
connect_timeout: float,
default_timeout: float,
additional_headers: dict[str, str] | None = None,
) -> None:
self._ws_url_for_task = ws_url_for_taskView on GitHub (pinned to 61d76f1ff3)
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
Defensive patterns
Strategy: retry
Type guard
def is_task_timeout(exc: BaseException) -> bool:
return isinstance(exc, TaskTimeoutError) Try / catch
from docling.service_client.exceptions import TaskTimeoutError
try:
status = watcher.wait_for_terminal(task_id)
except TaskTimeoutError as exc:
if 'emit status updates' in str(exc):
status = watcher.wait_for_terminal(task_id, timeout=timeout_s * 3) Prevention
- Never pass a timeout smaller than one poll cycle
- Use the default timeout unless you know the poll cadence
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out waiting for task {task_id} after {wait_timeout:.2f
- Timed out waiting for task {task_id} after {timeout:.2f}s.
- gRPC infer call failed for model {self.model_name}: {exc}
- Task {task_id} was not found.
- Result for task {task_id} is not ready.
AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14).
Data as JSON: /api/errors/0e1e5ecb90d34613.
Report an issue: GitHub.