⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Conversation

@charles-dyfis-net
Copy link

Description

Motivation

Hook providers need the ability to distinguish between expected errors (like validation failures that the model should adjust for and retry) and unexpected errors (like assertion failures or configuration errors that should fail the task immediately). Currently, AfterToolCallEvent.exception is always None for @tool-decorated functions because the decorator catches all exceptions and converts them to error results before the executor can see them. This forces all tool errors to be returned to the model, even when propagating the exception would be more appropriate.

Resolves: #1565

Public API Changes

ToolResultEvent now accepts an optional exception parameter, and AfterToolCallEvent.exception is populated for decorated tools:

# Before: exception is always None for decorated tools
class MyHook(HookProvider):
    def register_hooks(self, registry, **kwargs):
        registry.add_callback(AfterToolCallEvent, self._check)

    def _check(self, event: AfterToolCallEvent):
        # event.exception is always None for @tool functions
        # even when the tool raised an exception
        pass

# After: exception is available for inspection
class PropagateUnexpectedExceptions(HookProvider):
    def __init__(self, allowed_exceptions=(ValueError,)):
        self.allowed_exceptions = allowed_exceptions

    def register_hooks(self, registry, **kwargs):
        registry.add_callback(AfterToolCallEvent, self._check)

    def _check(self, event: AfterToolCallEvent):
        if event.exception is None:
            return
        if isinstance(event.exception, self.allowed_exceptions):
            return  # Let model retry
        raise event.exception  # Propagate unexpected errors

The error result is still returned to the model by default; hooks must explicitly re-raise if they want to propagate.

Use Cases

  • Fail-fast on unexpected errors: Propagate AssertionError, ConfigurationError, etc. instead of letting the model retry futilely
  • Error classification: Log or handle different exception types differently based on their class
  • Selective retry: Allow ValueError to return to the model while propagating other exceptions

Related Issues

#1565

Documentation PR

strands-agents/docs#482

Type of Change

New feature

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@github-actions github-actions bot added size/m and removed size/m labels Jan 28, 2026
Copy link
Member

@zastrowm zastrowm left a comment

Choose a reason for hiding this comment

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

Good implementation! Left some comments/requested-changes of the tests

Comment on lines +504 to +506
assert after_event.exception is not None
assert isinstance(after_event.exception, ValueError)
assert "decorated tool error" in str(after_event.exception)
Copy link
Member

Choose a reason for hiding this comment

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

Can this be simplified to

Suggested change
assert after_event.exception is not None
assert isinstance(after_event.exception, ValueError)
assert "decorated tool error" in str(after_event.exception)
assert after_event.exception == ValueError("decorated tool error")

Or something similar? We should be able to verify the exact exception rather than "decorated tool error" in str(after_event.exception)", given that we control it.

Alternatively, do:

exception = ValueError("decorated tool error")

early on, and then check that it's:

assert after_event.exception == exception

Comment on lines +528 to +530
assert after_event.exception is not None
assert isinstance(after_event.exception, RuntimeError)
assert "runtime error from decorated tool" in str(after_event.exception)
Copy link
Member

Choose a reason for hiding this comment

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

Along the lines of my earlier comment, let's simplify this assertion

after_event = hook_events[-1]
assert isinstance(after_event, AfterToolCallEvent)
assert after_event.exception is None
assert after_event.result["status"] == "success"
Copy link
Member

Choose a reason for hiding this comment

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

nit: newline after this I think



@pytest.mark.asyncio
async def test_executor_stream_decorated_tool_exception_in_hook(
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a test case that ensures that exception is None, but the result is failed if the decorator returns an error state without throwing? I'd like to ensure that behavior is solidified in a test

@zastrowm
Copy link
Member

Looks like there are also some linting failures; uv run hatch run prepare is my goto for fixing/checking

@github-actions github-actions bot added size/m and removed size/m labels Jan 28, 2026
@dbschmigelski
Copy link
Member

Looks like there are also some linting failures; uv run hatch run prepare is my goto for fixing/checking

Yeah thats my bad, I optimistically tried to resolve conflicts in the UI. Should be good now

@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 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.

[FEATURE] Allow decorators to selectively present exceptions from being returned to the model

3 participants