fix(tools): let raise through the parallel native path, guard all handlers

Chasing down CodeRabbit's note about callers of
execute_single_native_tool_call turned up a fifth place this exception was
being downgraded: the experimental executor's parallel branch wrapped
future.result() in a broad except and folded the abort into a fake tool
result, so the remaining parallel calls carried on. The sequential path and
crew_agent_executor's parallel branch were already fine.

Five separate handlers have swallowed this during review, so added a guard
test asserting the passthrough at every site rather than trusting the next
one gets spotted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETacm2dMASfpMAYUiDu5YG
This commit is contained in:
Joao Moura
2026-07-29 09:21:56 -07:00
parent 55160c68fd
commit e0f354e1a6
2 changed files with 30 additions and 0 deletions

View File

@@ -1767,6 +1767,10 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):
idx = future_to_idx[future]
try:
ordered_results[idx] = future.result()
except ToolExecutionFailedError:
# A deliberate stop: folding it into a tool result would
# let the remaining parallel calls carry on.
raise
except Exception as e:
tool_call = runnable_tool_calls[idx]
info = extract_tool_call_info(tool_call)

View File

@@ -700,6 +700,32 @@ class TestRaisePolicySurvivesEveryWrapper:
).kickoff()
assert not result.has_tool_failures
def test_every_broad_handler_around_tool_execution_lets_it_through(self) -> None:
"""Guard against a new `except Exception` quietly downgrading an abort.
Five separate handlers have swallowed this exception during review of
this PR, so assert the passthrough at each site rather than trusting
that the next one will be spotted.
"""
import inspect
from crewai.agent.core import Agent as AgentCls
from crewai.agents.crew_agent_executor import CrewAgentExecutor
from crewai.agents.step_executor import StepExecutor
from crewai.experimental.agent_executor import AgentExecutor
sites = [
(AgentCls._execute_with_timeout, "_passthrough_exceptions"),
(StepExecutor.execute, "ToolExecutionFailedError"),
(AgentExecutor.execute_tool_action, "ToolExecutionFailedError"),
(AgentExecutor.execute_native_tool, "ToolExecutionFailedError"),
(CrewAgentExecutor._invoke_loop_react, "ToolExecutionFailedError"),
(CrewAgentExecutor._ainvoke_loop_react, "ToolExecutionFailedError"),
]
for func, expected in sites:
source = inspect.getsource(func)
assert expected in source, f"{func.__qualname__} lost its passthrough"
def test_passthrough_tuple_includes_the_error(self) -> None:
from crewai.agent.core import _passthrough_exceptions