docling-project/docling · error · TaskTimeoutError

Timed out waiting for task {task_id} after {timeout:.2f}s.

Error message

Timed out waiting for task {task_id} after {timeout:.2f}s.

What it means

TaskTimeoutError raised inside the sync WebSocketWatcher's _iter_ws_connection loop when the monotonic deadline passes before a terminal task status arrives. The deadline is set from the watcher's default timeout or the per-call timeout, and it is checked before every websocket.recv(). It means the WebSocket stayed healthy but the task did not reach a success/failure status in time.

Source

Thrown at docling/service_client/watchers.py:237

                raise
            except Exception as exc:
                raise ServiceUnavailableError(
                    "WebSocket status stream is unavailable.", detail=str(exc)
                ) from exc

    def _iter_ws_connection(
        self, ws_url: str, task_id: str, deadline: float, timeout: float
    ) -> Iterator[TaskStatusResponse]:
        with connect(
            ws_url,
            open_timeout=self._connect_timeout,
            close_timeout=self._connect_timeout,
            additional_headers=self._additional_headers,
        ) as websocket:
            while True:
                remaining = deadline - time.monotonic()
                if remaining <= 0:
                    raise TaskTimeoutError(
                        f"Timed out waiting for task {task_id} after {timeout:.2f}s."
                    )

                raw_message = websocket.recv(timeout=remaining)
                envelope = WebsocketMessage.model_validate_json(raw_message)
                status = _process_ws_envelope(envelope, task_id)

                if status is None:
                    continue

                yield status
                if is_terminal_task_status(status):
                    return

                # Only send "next" for UPDATE messages.  The server sends
                # CONNECTION once before the update loop begins; sending
                # "next" in response to it would queue an extra token that
                # the server later consumes as a request for a post-terminal

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Pass a larger timeout to the wait call (e.g. wait_for_terminal(task_id, timeout=3600))
  2. Raise the watcher's default timeout when constructing the service client
  3. Check the task status on the server (GET /v1/status/poll/{task_id}) to see whether it eventually finished, then retrieve the result instead of resubmitting
  4. Reduce task size (fewer pages per request) so it completes within the budget

Example fix

# before
status = watcher.wait_for_terminal(task_id)  # default timeout

# after
status = watcher.wait_for_terminal(task_id, timeout=1800.0)
Defensive patterns

Strategy: retry

Validate before calling

# Estimate needed timeout from document size before waiting
estimated = max(default_timeout, num_pages * seconds_per_page)
status = watcher.wait_for_terminal(task_id, timeout=estimated)

Try / catch

from docling.service_client.exceptions import TaskTimeoutError

try:
    status = watcher.wait_for_terminal(task_id, timeout=1800)
except TaskTimeoutError:
    # Task may still finish server-side; check once without resubmitting
    status = poll_status_fn(task_id, 0.0)
    if not is_terminal_task_status(status):
        raise

Prevention

When it happens

Trigger: Calling wait_for_terminal()/iter_updates() on a WebSocketWatcher where the docling-serve task runs longer than the configured timeout (default_timeout or the timeout= argument), so remaining = deadline - time.monotonic() drops to <= 0 before is_terminal_task_status(status) is true.

Common situations: Large batch conversions or GPU-starved servers exceeding a default timeout; timeout left at library default for big workloads; server queue backlog; slow OCR models on CPU.

Understand the failure class

Related errors


AI-assisted analysis of docling-project/docling@61d76f1ff3 (2026-08-14). Data as JSON: /api/errors/0393d7d0936d098d. Report an issue: GitHub.