From 05b7697c351f55af4925286687dac4be3cc85cee Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:05:54 -0700 Subject: [PATCH] Fix: avoid IntegrityError when a retried task republishes with the same task_id (#14096) --- src/documents/signals/handlers.py | 17 +++++++++++------ src/documents/tests/test_task_signals.py | 11 +++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index 2e820c2b8..ec3f3778c 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -1189,13 +1189,18 @@ def before_task_publish_handler( trigger_source = _determine_trigger_source(headers) owner_id = _extract_owner_id(task_type, task_kwargs) - PaperlessTask.objects.create( + # A retried task is republished with the same task_id, so this fires + # again for it; get_or_create keeps the original PENDING record + # instead of raising a duplicate-key IntegrityError on the retry. + PaperlessTask.objects.get_or_create( task_id=task_id, - task_type=task_type, - trigger_source=trigger_source, - status=PaperlessTask.Status.PENDING, - input_data=input_data, - owner_id=owner_id, + defaults={ + "task_type": task_type, + "trigger_source": trigger_source, + "status": PaperlessTask.Status.PENDING, + "input_data": input_data, + "owner_id": owner_id, + }, ) except Exception: # pragma: no cover logger.exception("Creating PaperlessTask failed") diff --git a/src/documents/tests/test_task_signals.py b/src/documents/tests/test_task_signals.py index 5aded2d17..41856236f 100644 --- a/src/documents/tests/test_task_signals.py +++ b/src/documents/tests/test_task_signals.py @@ -106,6 +106,17 @@ class TestBeforeTaskPublishHandler: assert task.task_type == PaperlessTask.TaskType.TRAIN_CLASSIFIER assert task.trigger_source == PaperlessTask.TriggerSource.MANUAL + # A Celery retry republishes with the same task_id; this must not + # raise a duplicate-key IntegrityError, and must leave the original + # PENDING record alone. + send_publish( + "documents.tasks.train_classifier", + (), + {}, + headers={"id": task_id}, + ) + assert PaperlessTask.objects.filter(task_id=task_id).count() == 1 + def test_creates_task_for_sanity_check(self) -> None: task_id = send_publish("documents.tasks.sanity_check", (), {}) task = PaperlessTask.objects.get(task_id=task_id)