From bd79e343c08fc2b7c5f38202fe475c39c0742474 Mon Sep 17 00:00:00 2001 From: Sharry Stowell Date: Thu, 2 Jul 2026 12:22:58 +0100 Subject: [PATCH 1/2] fix(tools): replace bare raise with ToolUsageError in _original_tool_calling --- fix-bare-raise-tool-usage.patch | 62 +++++++++++++++++++++++ lib/crewai/src/crewai/tools/tool_usage.py | 4 +- lib/crewai/tests/tools/test_tool_usage.py | 39 ++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 fix-bare-raise-tool-usage.patch diff --git a/fix-bare-raise-tool-usage.patch b/fix-bare-raise-tool-usage.patch new file mode 100644 index 0000000000..c84dd4aca5 --- /dev/null +++ b/fix-bare-raise-tool-usage.patch @@ -0,0 +1,62 @@ +diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py +index e92ba03..8ad416f 100644 +--- a/lib/crewai/src/crewai/tools/tool_usage.py ++++ b/lib/crewai/src/crewai/tools/tool_usage.py +@@ -850,7 +850,9 @@ class ToolUsage: + + if not isinstance(arguments, dict): + if raise_error: +- raise ++ raise ToolUsageError( ++ f"{I18N_DEFAULT.errors('tool_arguments_error')}" ++ ) + return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") + + return ToolCalling( +diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py +index 9d61c93..c75b6ee 100644 +--- a/lib/crewai/tests/tools/test_tool_usage.py ++++ b/lib/crewai/tests/tools/test_tool_usage.py +@@ -903,3 +903,42 @@ def test_tool_error_does_not_emit_finished_event(): + assert len(finished_events) == 0, ( + "ToolUsageFinishedEvent should NOT be emitted after ToolUsageErrorEvent" + ) ++ ++ ++def test_original_tool_calling_non_dict_arguments_raises_tool_usage_error(): ++ """Regression test: a non-dict tool input with raise_error=True must raise ++ ToolUsageError, not RuntimeError('No active exception to re-raise'). ++ ++ Previously this path used a bare ``raise`` outside any except block. ++ """ ++ from unittest.mock import patch ++ ++ from crewai.tools import tool as tool_decorator ++ from crewai.tools.tool_usage import ToolUsage, ToolUsageError ++ ++ @tool_decorator("dummy_tool") ++ def dummy_tool(x: str) -> str: ++ """A dummy tool.""" ++ return x ++ ++ action = MagicMock() ++ action.tool = "dummy_tool" ++ action.tool_input = '["not", "a", "dict"]' ++ ++ tool_usage = ToolUsage( ++ tools_handler=MagicMock(), ++ tools=[dummy_tool], ++ task=MagicMock(), ++ function_calling_llm=None, ++ agent=MagicMock(), ++ action=action, ++ ) ++ ++ with ( ++ patch.object(tool_usage, "_select_tool", return_value=dummy_tool), ++ patch.object( ++ tool_usage, "_validate_tool_input", return_value=["not", "a", "dict"] ++ ), ++ pytest.raises(ToolUsageError), ++ ): ++ tool_usage._original_tool_calling(action.tool_input, raise_error=True) diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py index e92ba03eed..8ad416ff50 100644 --- a/lib/crewai/src/crewai/tools/tool_usage.py +++ b/lib/crewai/src/crewai/tools/tool_usage.py @@ -850,7 +850,9 @@ def _original_tool_calling( if not isinstance(arguments, dict): if raise_error: - raise + raise ToolUsageError( + f"{I18N_DEFAULT.errors('tool_arguments_error')}" + ) return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") return ToolCalling( diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py index 9d61c93a9b..c75b6ee76e 100644 --- a/lib/crewai/tests/tools/test_tool_usage.py +++ b/lib/crewai/tests/tools/test_tool_usage.py @@ -903,3 +903,42 @@ def on_finished(source, event): assert len(finished_events) == 0, ( "ToolUsageFinishedEvent should NOT be emitted after ToolUsageErrorEvent" ) + + +def test_original_tool_calling_non_dict_arguments_raises_tool_usage_error(): + """Regression test: a non-dict tool input with raise_error=True must raise + ToolUsageError, not RuntimeError('No active exception to re-raise'). + + Previously this path used a bare ``raise`` outside any except block. + """ + from unittest.mock import patch + + from crewai.tools import tool as tool_decorator + from crewai.tools.tool_usage import ToolUsage, ToolUsageError + + @tool_decorator("dummy_tool") + def dummy_tool(x: str) -> str: + """A dummy tool.""" + return x + + action = MagicMock() + action.tool = "dummy_tool" + action.tool_input = '["not", "a", "dict"]' + + tool_usage = ToolUsage( + tools_handler=MagicMock(), + tools=[dummy_tool], + task=MagicMock(), + function_calling_llm=None, + agent=MagicMock(), + action=action, + ) + + with ( + patch.object(tool_usage, "_select_tool", return_value=dummy_tool), + patch.object( + tool_usage, "_validate_tool_input", return_value=["not", "a", "dict"] + ), + pytest.raises(ToolUsageError), + ): + tool_usage._original_tool_calling(action.tool_input, raise_error=True) From 427db85238903de110913cfdd8906ca2021a318e Mon Sep 17 00:00:00 2001 From: Sharry Stowell Date: Thu, 2 Jul 2026 12:26:25 +0100 Subject: [PATCH 2/2] remove .patch file --- fix-bare-raise-tool-usage.patch | 62 --------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 fix-bare-raise-tool-usage.patch diff --git a/fix-bare-raise-tool-usage.patch b/fix-bare-raise-tool-usage.patch deleted file mode 100644 index c84dd4aca5..0000000000 --- a/fix-bare-raise-tool-usage.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py -index e92ba03..8ad416f 100644 ---- a/lib/crewai/src/crewai/tools/tool_usage.py -+++ b/lib/crewai/src/crewai/tools/tool_usage.py -@@ -850,7 +850,9 @@ class ToolUsage: - - if not isinstance(arguments, dict): - if raise_error: -- raise -+ raise ToolUsageError( -+ f"{I18N_DEFAULT.errors('tool_arguments_error')}" -+ ) - return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}") - - return ToolCalling( -diff --git a/lib/crewai/tests/tools/test_tool_usage.py b/lib/crewai/tests/tools/test_tool_usage.py -index 9d61c93..c75b6ee 100644 ---- a/lib/crewai/tests/tools/test_tool_usage.py -+++ b/lib/crewai/tests/tools/test_tool_usage.py -@@ -903,3 +903,42 @@ def test_tool_error_does_not_emit_finished_event(): - assert len(finished_events) == 0, ( - "ToolUsageFinishedEvent should NOT be emitted after ToolUsageErrorEvent" - ) -+ -+ -+def test_original_tool_calling_non_dict_arguments_raises_tool_usage_error(): -+ """Regression test: a non-dict tool input with raise_error=True must raise -+ ToolUsageError, not RuntimeError('No active exception to re-raise'). -+ -+ Previously this path used a bare ``raise`` outside any except block. -+ """ -+ from unittest.mock import patch -+ -+ from crewai.tools import tool as tool_decorator -+ from crewai.tools.tool_usage import ToolUsage, ToolUsageError -+ -+ @tool_decorator("dummy_tool") -+ def dummy_tool(x: str) -> str: -+ """A dummy tool.""" -+ return x -+ -+ action = MagicMock() -+ action.tool = "dummy_tool" -+ action.tool_input = '["not", "a", "dict"]' -+ -+ tool_usage = ToolUsage( -+ tools_handler=MagicMock(), -+ tools=[dummy_tool], -+ task=MagicMock(), -+ function_calling_llm=None, -+ agent=MagicMock(), -+ action=action, -+ ) -+ -+ with ( -+ patch.object(tool_usage, "_select_tool", return_value=dummy_tool), -+ patch.object( -+ tool_usage, "_validate_tool_input", return_value=["not", "a", "dict"] -+ ), -+ pytest.raises(ToolUsageError), -+ ): -+ tool_usage._original_tool_calling(action.tool_input, raise_error=True)