Skip to content

bulk_update_documents crashes with DoesNotExist in build_document_node when a correspondent/document type is deleted concurrently (LLM index enabled) #13314

Description

@jensRaspe

Description

When the LLM index (AI features) is enabled, documents.tasks.bulk_update_documents can crash with Correspondent matching query does not exist (or DocumentType matching query does not exist) if the referenced correspondent or document type is deleted while the task's update_llm_index pass is running.

The metadata change itself is committed correctly (the foreign key is set to NULL as expected, no orphaned rows remain), but the LLM-index update for the affected documents is aborted, leaving their vector nodes stale until the next document_llmindex run. It is a small unguarded foreign-key access in build_document_node, present in both the latest release (3.0.3) and main.

Background & real-world use case (non-technical)

Paperless-ngx can build an LLM index — a semantic/vector index over your documents that powers the AI features (similar-document suggestions, RAG-based Q&A). Whenever a document changes, its entry in this index is refreshed so the AI stays in sync with the library.

A very common maintenance activity is tidying up the taxonomy — e.g. merging duplicate correspondents (say, "ACME Ltd" and "ACME Limited" into one) or removing a document type you no longer use. When this is done across many documents at once, Paperless queues background jobs that refresh each affected document, including its LLM-index entry.

The problem: if a correspondent or document type is deleted at roughly the same time these background refresh jobs run for documents that still reference it, the LLM-index refresh crashes for those documents. The documents themselves are fine — the correspondent/type is removed cleanly and nothing is lost — but the AI index for those few documents is left slightly out of date until the next index run, and the logs fill with an error traceback.

In short: this hits users who (a) have the AI/LLM index enabled and (b) do bulk taxonomy cleanup. It surfaced here during a large re-classification run that reassigned/merged correspondents and document types across the whole library.

Environment

  • Paperless-ngx: 3.0.3 (also reproducible against main by code inspection — see Notes)
  • Deployment: Docker (official ghcr.io image)
  • AI features: LLM index enabled (embedding backend configured)
  • Database: PostgreSQL

Traceback (abridged)

[ERROR] [celery.app.trace] Task documents.tasks.bulk_update_documents[…] raised unexpected:
    DoesNotExist('Correspondent matching query does not exist.')
  ...
  File "src/documents/tasks.py", line 332, in bulk_update_documents
    update_llm_index(rebuild=False)
  File "src/paperless_ai/indexing.py", line 378, in update_llm_index
    nodes = build_document_node(document, chunk_size=chunk_size)
  File "src/paperless_ai/indexing.py", line 184, in build_document_node
    if document.correspondent
  File ".../django/db/models/fields/related_descriptors.py", line 224, in get_object
    ...
django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist:
    Correspondent matching query does not exist.

The same crash occurs for document_type (indexing.py:187).

Root cause

build_document_node accesses the related objects with an unguarded lazy load:

"correspondent": document.correspondent.name if document.correspondent else None,
"document_type": document.document_type.name if document.document_type else None,
"storage_path": document.storage_path.name if document.storage_path else None,

if document.correspondent triggers a lazy DB fetch by correspondent_id. If that correspondent was deleted between the time the document was read for the index pass and the time build_document_node resolves the relation, the fetch raises RelatedObjectDoesNotExist instead of returning None, and the whole update_llm_index pass then aborts for the remaining documents in that task.

Steps to reproduce

  1. Enable the LLM index.
  2. Assign a correspondent (or document type) to one or more documents.
  3. Trigger a bulk_edit on those documents (which enqueues bulk_update_documents) and delete that correspondent/document type at around the same time.
  4. The bulk_update_documents task raises DoesNotExist in build_document_node.

Impact

  • No data loss: the bulk metadata change is committed, FKs are nulled correctly, no orphaned rows.
  • The LLM index nodes for the affected documents are not updated until the next document_llmindex update/rebuild.
  • Noisy ERROR tracebacks during otherwise-normal bulk taxonomy maintenance.

Proposed fix

Either of:

  1. Re-fetch the document freshly at task start so the instance reflects the committed NULL FK, or
  2. Guard the relation access in build_document_node, treating a missing related object the same as None:
from django.core.exceptions import ObjectDoesNotExist

def _safe_related_name(document, field):
    try:
        rel = getattr(document, field)
    except ObjectDoesNotExist:
        return None
    return rel.name if rel else None

Option 2 is minimal and keeps node building resilient regardless of concurrency.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendbugBug report or a Bug-fix

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions