Skip to content

fix: set StatusCode.ERROR on tool spans when tool result status is error#2027

Open
nicolas-bezdolya wants to merge 1 commit intostrands-agents:mainfrom
nicolas-bezdolya:fix/tool-span-error-status
Open

fix: set StatusCode.ERROR on tool spans when tool result status is error#2027
nicolas-bezdolya wants to merge 1 commit intostrands-agents:mainfrom
nicolas-bezdolya:fix/tool-span-error-status

Conversation

@nicolas-bezdolya
Copy link
Copy Markdown

Problem

When a tool raises an exception, Strands catches it in _stream() and converts it to a ToolResult dict with status='error'. The original exception object is lost before end_tool_call_span is called, so _end_span() always falls into the else branch and marks the span as StatusCode.OK.

The gen_ai.tool.status: "error" attribute IS correctly set, but the span StatusCode remains OK. This is critical because observability backends like Langfuse rely on StatusCode.ERROR to raise alarms/statistics on failed tool calls.

Solution

In end_tool_call_span(), before calling _end_span(), check if tool_result["status"] == "error" and synthesize an Exception from the error content. This ensures the span correctly gets StatusCode.ERROR and record_exception() is called.

Testing

  • All 76 tracer tests pass ✅
  • All 30 executor tests pass ✅

Fixes #2016

When a tool raises an exception, Strands catches it and converts it to a
ToolResult dict with status='error'. The original exception object is lost
before end_tool_call_span is called, so the span always gets StatusCode.OK.

This fix synthesizes an Exception from the error content in the tool result
when no explicit error is passed, ensuring the span correctly gets
StatusCode.ERROR. This is critical for observability backends like Langfuse
that rely on span status codes for error detection.

Fixes strands-agents#2016
Copy link
Copy Markdown
Author

@nicolas-bezdolya nicolas-bezdolya left a comment

Choose a reason for hiding this comment

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

Fixed.

if error is None and tool_result is not None and tool_result.get("status") == "error":
content = tool_result.get("content", [])
error_message = content[0].get("text", "Tool call failed") if content else "Tool call failed"
error = Exception(error_message)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Issue: Missing test coverage for this new error-handling logic.

Suggestion: Add unit tests to verify:

  1. end_tool_call_span sets StatusCode.ERROR when tool_result["status"] == "error"
  2. The error message is correctly extracted from content[0]["text"]
  3. The default message "Tool call failed" is used when content is empty

Example test structure:

def test_end_tool_call_span_with_error_status(mock_span):
    """Test that tool spans get StatusCode.ERROR when tool result status is error."""
    tracer = Tracer()
    tool_result = {
        "status": "error",
        "content": [{"text": "Error: ValueError - something failed"}],
        "toolUseId": "test-id"
    }
    
    tracer.end_tool_call_span(mock_span, tool_result)
    
    mock_span.set_status.assert_called_once_with(
        StatusCode.ERROR, 
        "Error: ValueError - something failed"
    )
    mock_span.record_exception.assert_called_once()


def test_end_tool_call_span_with_error_status_empty_content(mock_span):
    """Test default error message when content is empty."""
    tracer = Tracer()
    tool_result = {"status": "error", "content": [], "toolUseId": "test-id"}
    
    tracer.end_tool_call_span(mock_span, tool_result)
    
    mock_span.set_status.assert_called_once_with(StatusCode.ERROR, "Tool call failed")

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

Assessment: Request Changes

This PR correctly addresses the issue where tool spans receive StatusCode.OK even when the tool fails. The fix properly synthesizes an exception from the tool_result["status"] == "error" condition so that _end_span sets the appropriate error status.

Review Details
  • Testing: The PR is missing unit tests for this new error-synthesis logic. Tests should verify the StatusCode.ERROR is set and exception is recorded when tool result status is "error".
  • Code Quality: The implementation is clean, minimal, and follows existing patterns.
  • Documentation: Not required for this bug fix.

The fix itself looks correct - please add tests to verify the behavior before merging.

},
)

# If no explicit error but tool_result indicates an error, synthesize one so the span gets StatusCode.ERROR
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

instead of fixing it here, shouldn't we fix it downstream so that the exception is propagated up the chain? Otherwise, we are doing exception -> text in the tool, and then text -> exception here.

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.

[BUG] Strands Swallows Tool Call Errors

2 participants