docling-project/docling · warning · TaskTimeoutError

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

Error message

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

What it means

TaskTimeoutError raised by PollingWatcher.iter_updates when the monotonic deadline (default_timeout or the per-call timeout) expires before the task reaches a terminal status. Each poll passes the remaining window to the server's long-poll; once remaining <= 0 the wait fails.

Source

Thrown at docling/service_client/watchers.py:114

        poll_client_interval: float | None,
        default_timeout: float,
    ) -> None:
        self._poll_status = poll_status
        self._poll_server_wait = poll_server_wait
        self._poll_client_interval = (
            poll_server_wait if poll_client_interval is None else poll_client_interval
        )
        self._default_timeout = default_timeout

    def iter_updates(
        self, task_id: str, timeout: float | None = None
    ) -> Iterator[TaskStatusResponse]:
        wait_timeout = self._default_timeout if timeout is None else timeout
        deadline = time.monotonic() + wait_timeout
        while True:
            remaining = deadline - time.monotonic()
            if remaining <= 0:
                raise TaskTimeoutError(
                    f"Timed out waiting for task {task_id} after {wait_timeout:.2f}s."
                )

            poll_wait = min(self._poll_server_wait, remaining)
            poll_started = time.monotonic()
            update = self._poll_status(task_id, poll_wait)
            yield update
            if is_terminal_task_status(update):
                return

            # 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)

View on GitHub (pinned to 61d76f1ff3)

Solutions

  1. Pass a larger timeout to wait_for_terminal (or raise the client's default) sized to real conversion time
  2. Check service queue depth/worker count — the task may simply be waiting in queue
  3. Note the task keeps running server-side: you may re-attach with iter_updates(task_id) instead of resubmitting

Example fix

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

# after
status = watcher.wait_for_terminal(task_id, timeout=1800.0)
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, timeout=timeout_s)
except TaskTimeoutError:
    status = watcher.wait_for_terminal(task_id, timeout=timeout_s)  # re-attach; task still runs

Prevention

When it happens

Trigger: wait_for_terminal()/iter_updates() on a task that runs longer than the configured timeout — big documents, queued behind many jobs, or a stuck task that never transitions.

Common situations: Default timeout too small for large batch conversions; overloaded single-worker service; task actually failed to progress (worker crash) and never terminates.

Understand the failure class

Related errors


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