Skip to content

Commit d530240

Browse files
committed
fix: prune stale tool results during session repair
1 parent 1232230 commit d530240

2 files changed

Lines changed: 56 additions & 6 deletions

File tree

src/strands/session/repository_session_manager.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,34 @@ def _fix_broken_tool_use(self, messages: list[Message]) -> list[Message]:
282282
]
283283

284284
# Check if there are more messages after the current toolUse message
285-
tool_result_ids = [
286-
content["toolResult"]["toolUseId"]
287-
for content in messages[index + 1]["content"]
288-
if "toolResult" in content
289-
]
285+
next_message_content = messages[index + 1]["content"]
286+
seen_tool_result_ids: set[str] = set()
287+
cleaned_next_message_content = []
288+
removed_orphaned_tool_results = False
289+
for content in next_message_content:
290+
if "toolResult" not in content:
291+
cleaned_next_message_content.append(content)
292+
continue
293+
294+
tool_result_id = content["toolResult"]["toolUseId"]
295+
if tool_result_id in tool_use_ids and tool_result_id not in seen_tool_result_ids:
296+
seen_tool_result_ids.add(tool_result_id)
297+
cleaned_next_message_content.append(content)
298+
else:
299+
removed_orphaned_tool_results = True
290300

291-
missing_tool_use_ids = list(set(tool_use_ids) - set(tool_result_ids))
301+
if removed_orphaned_tool_results:
302+
logger.warning(
303+
"Session message history has orphaned or duplicate toolResult blocks. "
304+
"Removing them to keep toolUse/toolResult pairs valid."
305+
)
306+
messages[index + 1]["content"] = cleaned_next_message_content
307+
308+
tool_result_ids = list(seen_tool_result_ids)
309+
310+
missing_tool_use_ids = [
311+
tool_use_id for tool_use_id in tool_use_ids if tool_use_id not in tool_result_ids
312+
]
292313
# If there are missing tool use ids, that means the messages history is broken
293314
if missing_tool_use_ids:
294315
logger.warning(

tests/strands/session/test_repository_session_manager.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,35 @@ def test_fix_broken_tool_use_extends_partial_tool_results(existing_session_manag
370370
assert missing_result["toolResult"]["content"][0]["text"] == "Tool was interrupted."
371371

372372

373+
def test_fix_broken_tool_use_prunes_extra_tool_results(session_manager):
374+
"""Test fixing a user message with extra toolResults before adding missing ones."""
375+
messages = [
376+
{
377+
"role": "assistant",
378+
"content": [
379+
{"toolUse": {"toolUseId": "complete-123", "name": "test_tool", "input": {"input": "test1"}}},
380+
{"toolUse": {"toolUseId": "missing-456", "name": "test_tool", "input": {"input": "test2"}}},
381+
],
382+
},
383+
{
384+
"role": "user",
385+
"content": [
386+
{"toolResult": {"toolUseId": "complete-123", "status": "success", "content": [{"text": "result"}]}},
387+
{"toolResult": {"toolUseId": "complete-123", "status": "success", "content": [{"text": "dup"}]}},
388+
{"toolResult": {"toolUseId": "stale-999", "status": "success", "content": [{"text": "stale"}]}},
389+
],
390+
},
391+
]
392+
393+
fixed_messages = session_manager._fix_broken_tool_use(messages)
394+
395+
tool_results = [content["toolResult"] for content in fixed_messages[1]["content"] if "toolResult" in content]
396+
assert [tool_result["toolUseId"] for tool_result in tool_results] == ["complete-123", "missing-456"]
397+
missing_result = next(tool_result for tool_result in tool_results if tool_result["toolUseId"] == "missing-456")
398+
assert missing_result["status"] == "error"
399+
assert missing_result["content"][0]["text"] == "Tool was interrupted."
400+
401+
373402
def test_fix_broken_tool_use_handles_multiple_orphaned_tools(existing_session_manager):
374403
"""Test fixing multiple orphaned toolUse messages."""
375404

0 commit comments

Comments
 (0)