From 223c14f48bfa647e05bc84e7362390e5617bbedd Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 8 Mar 2026 03:16:50 +0100 Subject: [PATCH] fix: add deterministic tiebreaker to all paginated chat queries (#22387) Add Chat.id as a secondary sort key to all paginated chat queries that use offset/limit pagination. When multiple chats share the same updated_at timestamp, the database does not guarantee a stable order across page boundaries, causing chats to appear on multiple pages. This produces duplicate keys in the Svelte sidebar each-block (each_key_duplicate error). Adding Chat.id as a tiebreaker ensures fully deterministic ordering. Extends the fix from #22383 (which addressed get_chat_ids_by_model_id) to all remaining paginated chat queries. --- backend/open_webui/models/chats.py | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 8c6eb830b5..9b8261b8d4 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -734,13 +734,13 @@ class ChatTable: raise ValueError("Invalid order_by field") if direction.lower() == "asc": - query = query.order_by(getattr(Chat, order_by).asc()) + query = query.order_by(getattr(Chat, order_by).asc(), Chat.id) elif direction.lower() == "desc": - query = query.order_by(getattr(Chat, order_by).desc()) + query = query.order_by(getattr(Chat, order_by).desc(), Chat.id) else: raise ValueError("Invalid direction for ordering") else: - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) query = query.with_entities( Chat.id, Chat.title, Chat.updated_at, Chat.created_at @@ -793,13 +793,13 @@ class ChatTable: raise ValueError("Invalid order_by field") if direction.lower() == "asc": - query = query.order_by(getattr(Chat, order_by).asc()) + query = query.order_by(getattr(Chat, order_by).asc(), Chat.id) elif direction.lower() == "desc": - query = query.order_by(getattr(Chat, order_by).desc()) + query = query.order_by(getattr(Chat, order_by).desc(), Chat.id) else: raise ValueError("Invalid direction for ordering") else: - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) # Select only the columns needed for SharedChatResponse # to avoid loading the heavy chat JSON blob @@ -854,13 +854,13 @@ class ChatTable: if order_by and direction and getattr(Chat, order_by): if direction.lower() == "asc": - query = query.order_by(getattr(Chat, order_by).asc()) + query = query.order_by(getattr(Chat, order_by).asc(), Chat.id) elif direction.lower() == "desc": - query = query.order_by(getattr(Chat, order_by).desc()) + query = query.order_by(getattr(Chat, order_by).desc(), Chat.id) else: raise ValueError("Invalid direction for ordering") else: - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) if skip: query = query.offset(skip) @@ -892,7 +892,7 @@ class ChatTable: if not include_archived: query = query.filter_by(archived=False) - query = query.order_by(Chat.updated_at.desc()).with_entities( + query = query.order_by(Chat.updated_at.desc(), Chat.id).with_entities( Chat.id, Chat.title, Chat.updated_at, Chat.created_at ) @@ -1039,14 +1039,14 @@ class ChatTable: if order_by and direction: if hasattr(Chat, order_by): if direction.lower() == "asc": - query = query.order_by(getattr(Chat, order_by).asc()) + query = query.order_by(getattr(Chat, order_by).asc(), Chat.id) elif direction.lower() == "desc": - query = query.order_by(getattr(Chat, order_by).desc()) + query = query.order_by(getattr(Chat, order_by).desc(), Chat.id) else: - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) else: - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) total = query.count() @@ -1188,7 +1188,7 @@ class ChatTable: if folder_ids: query = query.filter(Chat.folder_id.in_(folder_ids)) - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) # Check if the database dialect is either 'sqlite' or 'postgresql' dialect_name = db.bind.dialect.name @@ -1309,7 +1309,7 @@ class ChatTable: query = query.filter(or_(Chat.pinned == False, Chat.pinned == None)) query = query.filter_by(archived=False) - query = query.order_by(Chat.updated_at.desc()) + query = query.order_by(Chat.updated_at.desc(), Chat.id) if skip: query = query.offset(skip)