From bfb08e77f8f44e35674c9350b8dd733bd743f57f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:11:52 -0800 Subject: [PATCH] fix: prevent deadlock in bulk tool upsert by sorting tools by name (#8667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When multiple concurrent transactions try to upsert the same tools, they can deadlock if they acquire row locks in different orders. This fix sorts tools by name before the bulk INSERT to ensure all transactions acquire locks in a consistent order, preventing deadlocks. Fixes #8666 🤖 Generated with [Letta Code](https://letta.com) Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com> Co-authored-by: datadog-official[bot] Co-authored-by: Letta Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com> --- letta/services/tool_manager.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/letta/services/tool_manager.py b/letta/services/tool_manager.py index 0a5baafb..9a1e10a3 100644 --- a/letta/services/tool_manager.py +++ b/letta/services/tool_manager.py @@ -1189,12 +1189,19 @@ class ToolManager: from sqlalchemy import func, select from sqlalchemy.dialects.postgresql import insert + # Sort tools by name to prevent deadlocks. + # When multiple concurrent transactions try to upsert the same tools, + # they must acquire row locks in a consistent order to avoid deadlocks. + # Without sorting, Transaction A might lock (a, b, c) while Transaction B + # locks (b, c, a), causing each to wait for the other (deadlock). + sorted_tool_data_list = sorted(tool_data_list, key=lambda t: t.name) + # prepare data for bulk insert table = ToolModel.__table__ valid_columns = {col.name for col in table.columns} insert_data = [] - for tool in tool_data_list: + for tool in sorted_tool_data_list: tool_dict = tool.model_dump(to_orm=True) # set created/updated by fields if actor: