Skip to content

Handle multiple messages for one taskId - #712

Merged
julien-nc merged 2 commits into
mainfrom
fix/noid/multiple-messages-for-one-taskid
Oct 23, 2026
Merged

julien-nc merged 2 commits into
mainfrom
fix/noid/multiple-messages-for-one-taskid

Conversation

@julien-nc

@julien-nc julien-nc commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Fixes the retrieval of a chat message for one task ID so check_generation does not fail when the chat task ran twice.

This PR should be backported to assistant's stable34 (supports 33->34).

This should not happen in Nextcloud >= 34 thanks to nextcloud/server#62243 which could be backported to stable33.

Problem

When asking something in a new chat, the Assistant sometimes fails to show the answer. The generation indicator spins and never resolves the response. Pressing the "generate" button again works.
Reported for Nextcloud 33.0.8 in nextcloud-enterprise-aio.

The server log shows the following error on the check_generation polling endpoint:

MultipleObjectsReturnedException: Did not expect more than one result when executing query "... FROM PREFIXassistant_chat_msgs WHERE session_id = ? AND ocp_task_id = ?"

Root cause

The chat message for a completed task is inserted once per TaskSuccessfulEvent in ChattyLLMTaskListener. On Nextcloud 33, task processing executors fetch scheduled tasks non-atomically (SynchronousBackgroundJob and the OCS API used by task processing workers), so in a setup with several executors (e.g. the AIO cron container plus the taskprocessingworker container) the same task can be processed twice. Each execution dispatches TaskSuccessfulEvent, so the same assistant message ends up twice in oc_assistant_chat_msgs with the same ocp_task_id.

The frontend then polls check_generation, whose MessageMapper::getMessageByTaskId() uses findEntity() and throws MultipleObjectsReturnedException as soon as more than one row matches. The exception was not caught, resulting in a 500 for every poll of the affected task. Regenerating schedules a new task, which is why the second attempt succeeds.

Note: the atomic task claim (fix(TaskProcessing): Claim tasks atomically in both SynchronousBackgroundJob and TaskProcessingApiController, #62243) only landed in server 35 and is not backported to stable33, so the underlying race remains on NC 33.

Fix

  • Make ChattyLLMTaskListener idempotent: skip the insert if a message with the same ocp_task_id is already stored for the session.
  • Make MessageMapper::getMessageByTaskId() tolerant of duplicate rows (ORDER BY id LIMIT 1) so existing duplicates keep being pollable while the affected instance's data is cleaned up.

Testing

  • New unit tests in tests/unit/Listener/ChattyLLMTaskListenerTest.php cover the single insert, the idempotency when the event is dispatched twice and that title generation tasks are untouched.
  • Manually verified on an instance where a duplicated task had broken message polling.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI (N/A)

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

📝 Walkthrough

MessageMapper::getMessageByTaskId now returns the matching message with the lowest ID, or throws DoesNotExistException when no match exists. ChattyLLMTaskListener checks for an existing message before processing a task. The tests cover insertion when no message exists, skipping duplicate handling, and title-task handling. The controller docblock no longer declares MultipleObjectsReturnedException.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 4b7b5

This change stops the repeated-task polling failure and skips most duplicate message inserts. If two executors finish the same task at almost the same time, a duplicate assistant message can still appear. That is limited in scope and does not block merging, but the owner should be aware of it.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly describes the main change: handling duplicate messages associated with one task ID.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The pull request description directly explains the duplicate task processing issue, the resulting MultipleObjectsReturnedException, and the listener and mapper changes that address it.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: af0fcf7c-0da8-4e9e-8dad-a6058741499f
📥 Commits

Reviewing files that changed from the base of the PR and between d69ffeb and 4b7b555.

📒 Files selected for processing: 4
  • lib/Controller/ChattyLLMController.php
  • lib/Db/ChattyLLM/MessageMapper.php
  • lib/Listener/ChattyLLMTaskListener.php
  • tests/unit/Listener/ChattyLLMTaskListenerTest.php
💤 Files with no reviewable changes: 1
  • lib/Controller/ChattyLLMController.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment on lines +83 to +85
$this->messageMapper->getMessageByTaskId($sessionId, $task->getId());
return;
} catch (DoesNotExistException $e) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the duplicate check atomic with insertion.

When two executors handle the same task concurrently, both can receive DoesNotExistException before either reaches insert() at Line 158. Both then insert a message. The new check does not prevent the duplicate rows described in the PR. Use a database-enforced unique claim for the session and task, or a distributed lock that covers both the lookup and insertion. Account for existing duplicate rows before adding a unique constraint.

Concurrent task processing executors could run the same chat generation
task twice, storing the assistant message twice with the same ocp_task_id.
Polling check_generation then threw MultipleObjectsReturnedException,
leaving the user without a response until regenerating.

Make the message insertion idempotent in ChattyLLMTaskListener and make
getMessageByTaskId tolerant of duplicate rows.

Assisted-by: OpenCode:GLM-5.3-Flash
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
Cover the listener storing the assistant message only once when a task
was dispatched twice, and that title tasks are untouched.

Assisted-by: OpenCode:GLM-5.3-Flash
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
@julien-nc
julien-nc force-pushed the fix/noid/multiple-messages-for-one-taskid branch from 4b7b555 to 7d1e16a Compare October 22, 2026 14:24
@julien-nc
julien-nc merged commit ddc85f8 into main Oct 23, 2026
16 checks passed
@julien-nc
julien-nc deleted the fix/noid/multiple-messages-for-one-taskid branch October 23, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants