Skip to content

Python: [Bug]: async FunctionTool failures bypass max_invocation_exceptions #8277

Closed

Description

@CoralGarden52

Description

For an async FunctionTool, exceptions raised inside the coroutine body do not increment invocation_exception_count. As a result, max_invocation_exceptions is never enforced for async tools, although the public documentation describes it as the maximum number of exceptions allowed during invocations.

This can allow a failing async tool to be executed repeatedly after its configured failure budget is exhausted. The separate max_consecutive_errors_per_request loop setting is not equivalent: it is request-scoped and does not enforce the per-tool lifetime limit.

Reproduction

Run against upstream/main at c37de519b:

import asyncio
from agent_framework import FunctionTool

async def always_fails() -> str:
    raise RuntimeError("boom")

async def main() -> None:
    tool = FunctionTool(
        name="always_fails",
        func=always_fails,
        max_invocation_exceptions=1,
    )

    for attempt in range(1, 4):
        try:
            await tool.invoke(skip_parsing=True)
        except Exception as exc:
            print(
                attempt,
                type(exc).__name__,
                tool.invocation_count,
                tool.invocation_exception_count,
            )

asyncio.run(main())

Observed:

1 RuntimeError 1 0
2 RuntimeError 2 0
3 RuntimeError 3 0

For comparison, an equivalent synchronous function produces:

1 RuntimeError 1 1
2 ToolException 1 1

Expected behavior

After the first async invocation failure, the second invocation should raise ToolException when max_invocation_exceptions=1, and invocation_exception_count should report the failure.

Suspected cause

FunctionTool.__call__ catches exceptions only while creating the function result. For an async function, func(...) returns a coroutine successfully; the exception is raised later by await res in _invoke_function, outside the counter's try/except block.

Relevant code:

  • python/packages/core/agent_framework/_tools.py:595-632
  • python/packages/core/agent_framework/_tools.py:421-422

The repository already supports async tools through FunctionTool.invoke, and the option is not documented as sync-only.

Impact

Any application using async tools with max_invocation_exceptions can exceed the configured failure budget, potentially repeating failing or side-effectful network/database/MCP operations.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 11, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Sep 11, 2026
  3. github-actions commented on Sep 11, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    The bug reproduces in python/packages/core/agent_framework/_tools.py::FunctionTool._invoke_function at lines 624-632 when an async wrapped function raises from its coroutine body. FunctionTool.__call__ returns the coroutine before the exception occurs, so invocation_exception_count remains zero and later calls bypass max_invocation_exceptions. Minimal repro: wrap an always-raising async function with max_invocation_exceptions=1, invoke it twice with skip_parsing=True, and observe that both calls raise RuntimeError instead of the second raising ToolException.

    • Failing test: python/packages/core/tests/core/test_tools.py::test_async_tool_enforces_max_invocation_exceptions
    • Files examined: python/packages/core/agent_framework/_tools.py, python/packages/core/tests/core/test_tools.py, python/packages/core/pyproject.toml
    • Tests run: python/packages/core/tests/core/test_tools.py::test_async_tool_enforces_max_invocation_exceptions, standalone issue reproduction
    • Current version: 1.18.0
  4. added
    agentsUsage: [Issues, PRs], Target: Single agent
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions