Skip to content

Conversation

@cagataycali
Copy link
Member

Summary

This PR fixes issue #1610 where Browser Tool + Memory causes ValidationException after the first message in a session.

Problem

When using tools (especially Browser Tool) with session memory, context truncation or summarization may remove toolUse blocks while keeping their corresponding toolResult blocks. This creates "orphaned" toolResult blocks that cause:

ValidationException: The number of toolResult blocks at messages.N.content
exceeds the number of toolUse blocks of previous turn.

Error Scenario

  1. User sends message → Browser tool creates toolUse blocks
  2. Memory saves conversation history
  3. Context is truncated/summarized → toolUse blocks removed, toolResult kept
  4. User sends second message → ValidationException

This is a widespread issue affecting:

  • Anthropic Claude Code (#13964) - orphaned tool_result blocks after context truncation
  • Pydantic AI - similar ValidationException issues
  • OpenHands - tool use/result mismatch after summarization
  • LiteLLM - same pattern with Bedrock Converse API

Root Cause

The existing _fix_broken_tool_use method only handled:

  1. ✅ Orphaned toolResult at the START of conversation (first message)
  2. ✅ Orphaned toolUse (missing toolResult)
  3. ❌ Orphaned toolResult in the MIDDLE of conversation

Solution

Added _remove_orphaned_tool_results method that:

  1. Collects all toolUseId values from toolUse blocks in all messages
  2. Scans all messages for toolResult blocks
  3. Removes any toolResult whose toolUseId has no matching toolUse
  4. Handles mixed-content messages (keeps valid content, removes only orphaned)
  5. Drops empty messages that only contained orphaned toolResult blocks

Code Change

def _remove_orphaned_tool_results(self, messages: list[Message]) -> list[Message]:
    """Remove orphaned toolResult blocks that have no matching toolUse."""
    # Collect all toolUse IDs
    all_tool_use_ids: set[str] = set()
    for message in messages:
        for content in message.get("content", []):
            if "toolUse" in content:
                all_tool_use_ids.add(content["toolUse"]["toolUseId"])
    
    # Filter out orphaned toolResult blocks
    # ... (see full implementation)

Testing

Added 5 new tests covering:

Test Description
test_fix_broken_tool_use_removes_orphaned_tool_result_in_middle Core fix - orphaned in middle
test_fix_broken_tool_use_removes_multiple_orphaned_tool_results Multiple orphaned throughout
test_fix_broken_tool_use_keeps_valid_tool_results_removes_orphaned Mixed valid/orphaned
test_fix_broken_tool_use_removes_orphaned_from_mixed_content_message Text + orphaned toolResult
test_fix_broken_tool_use_browser_tool_memory_scenario Exact issue #1610 scenario

All 29 tests pass:

======================= 29 passed in 0.72s =======================

Files Changed

  • src/strands/session/repository_session_manager.py - Added _remove_orphaned_tool_results method
  • tests/strands/session/test_repository_session_manager.py - Added 5 new tests

Related Issues


Note: This fix is backward compatible and won't affect existing valid conversations. It only removes toolResult blocks that have no matching toolUse block anywhere in the conversation history.

…tion

This fix addresses issue strands-agents#1610 where Browser Tool + Memory causes ValidationException.

## Problem

When using tools (especially Browser Tool) with session memory, context truncation
or summarization may remove toolUse blocks while keeping their corresponding
toolResult blocks. This creates 'orphaned' toolResult blocks that cause:

```
ValidationException: The number of toolResult blocks at messages.N.content
exceeds the number of toolUse blocks of previous turn.
```

## Root Cause

The existing `_fix_broken_tool_use` method only handled:
1. Orphaned toolResult at the START of conversation
2. Orphaned toolUse (missing toolResult)

But it did NOT handle orphaned toolResult blocks in the MIDDLE of conversation.

## Solution

Added `_remove_orphaned_tool_results` method that:
1. Collects all toolUse IDs from all messages
2. Scans all messages for toolResult blocks
3. Removes any toolResult whose toolUseId has no matching toolUse
4. Handles mixed-content messages (keeps valid content, removes orphaned)
5. Drops empty messages that only contained orphaned toolResults

## Testing

Added 5 new tests covering:
- Orphaned toolResult in middle of conversation
- Multiple orphaned toolResults throughout conversation
- Mixed valid/orphaned toolResults in same conversation
- Mixed content messages (text + toolResult)
- Browser Tool + Memory scenario (exact issue reproduction)

## Related Issues

- strands-agents#1610: Browser Tool + Memory ValidationException
- Anthropic Claude Code #13964: orphaned tool_result after context truncation
- Similar issues in Pydantic AI, OpenHands, LiteLLM

Fixes strands-agents#1610
@github-actions github-actions bot added the size/m label Feb 2, 2026
@codecov
Copy link

codecov bot commented Feb 2, 2026

Codecov Report

❌ Patch coverage is 96.55172% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/strands/session/repository_session_manager.py 96.55% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

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.

Browser Tool + Memory: orphaned toolUse causing ValidationException after first message

1 participant