{"record":{"id":"376e353132dd64ba","repo":"docling-project/docling","slug":"task-task-id-was-not-found-376e35","errorCode":null,"errorMessage":"Task {task_id} was not found.","messagePattern":"Task (.+?) was not found\\.","errorType":"exception","errorClass":"TaskNotFoundError","httpStatus":null,"severity":"error","filePath":"docling/service_client/watchers.py","lineNumber":57,"sourceCode":") -> float:\n    elapsed = time.monotonic() - poll_started\n    remaining = deadline - time.monotonic()\n    if remaining <= 0:\n        return 0.0\n    return max(0.0, min(poll_interval, remaining) - elapsed)\n\n\ndef _process_ws_envelope(\n    envelope: WebsocketMessage,\n    task_id: str,\n) -> TaskStatusResponse | None:\n    \"\"\"Return the TaskStatusResponse from an envelope, or None for CONNECTION frames.\n\n    Raises TaskNotFoundError or ServiceUnavailableError on error frames.\n    \"\"\"\n    if envelope.error:\n        if envelope.error == \"Task not found.\":\n            raise TaskNotFoundError(f\"Task {task_id} was not found.\")\n        raise ServiceUnavailableError(\n            \"WebSocket status stream failed.\",\n            detail=envelope.error,\n        )\n    return envelope.task\n\n\nclass StatusWatcher(Protocol):\n    \"\"\"Protocol for job status watchers.\"\"\"\n\n    def iter_updates(\n        self, task_id: str, timeout: float | None\n    ) -> Iterator[TaskStatusResponse]: ...\n\n    def wait_for_terminal(\n        self, task_id: str, timeout: float | None\n    ) -> TaskStatusResponse: ...\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/service_client/watchers.py#L39-L75","documentation":"TaskNotFoundError raised by the WebSocket status watcher when the service sends an error frame with exactly 'Task not found.' over WS /v1/status/ws/{task_id}. It means the task id is unknown to the service — expired from the result backend, never existed, or was purged after completion.","triggerScenarios":"Opening the status WebSocket for a task id that finished and was evicted, a fabricated/typo'd id, or a task record lost after a service restart while the client was waiting.","commonSituations":"Waiting on tasks across a service redeploy (in-memory task store wiped); result TTL expiry before the client re-attached; copy-paste errors in persisted task ids.","solutions":["Handle TaskNotFoundError as terminal — resubmit the conversion instead of re-polling","If tasks must survive restarts, configure a persistent result backend (redis/database) in docling-serve","Store task ids only as long as the service's task TTL allows"],"exampleFix":"try:\n    status = watcher.wait_for_terminal(task_id)\nexcept TaskNotFoundError:\n    result = client.submit(Path('doc.pdf'))  # resubmit","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_task_not_found(exc: BaseException) -> bool:\n    return isinstance(exc, TaskNotFoundError)","tryCatchPattern":"from docling.service_client.exceptions import TaskNotFoundError\n\ntry:\n    status = watcher.wait_for_terminal(task_id)\nexcept TaskNotFoundError:\n    task_id = resubmit_and_get_new_id()","preventionTips":["Resubmit on TaskNotFoundError instead of re-polling — the id is gone","Use a persistent task/result backend in docling-serve if ids must survive restarts","Consume task results before the service's task TTL expires"],"tags":["websocket","task-lifecycle","not-found","watchers"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}