Skip to content

fix(tools): raise ToolUsageError instead of bare raise for non-dict arguments#6499

Open
nolanchic wants to merge 1 commit into
crewAIInc:mainfrom
nolanchic:fix/tool-usage-bare-raise
Open

fix(tools): raise ToolUsageError instead of bare raise for non-dict arguments#6499
nolanchic wants to merge 1 commit into
crewAIInc:mainfrom
nolanchic:fix/tool-usage-bare-raise

Conversation

@nolanchic

Copy link
Copy Markdown

Description

ToolUsage._original_tool_calling() used a bare raise outside any active except block when the parsed tool arguments were not a dict:

if not isinstance(arguments, dict):
    if raise_error:
        raise          # <- no active exception here
    return ToolUsageError(...)

A bare raise re-raises the exception currently being handled, but on this path the preceding try/except completed normally, so no exception is active. Result: with raise_error=True the call raised RuntimeError: No active exception to reraise instead of the intended ToolUsageError, masking the real tool-arguments problem.

Fix: raise the explicit ToolUsageError, matching the message already used by the raise_error=False branch on the next line.

if not isinstance(arguments, dict):
    if raise_error:
        raise ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}")
    return ToolUsageError(f"{I18N_DEFAULT.errors('tool_arguments_error')}")

Why the bug was latent

_validate_tool_input() currently always returns a dict or raises, so the non-dict branch was effectively dead code in normal use. The bug surfaces whenever that contract is relaxed — e.g. subclasses overriding _validate_tool_input, monkeypatching in tests, or future changes to the validation pipeline. Making the branch raise the correct exception keeps the failure mode meaningful instead of an opaque RuntimeError.

Test plan

  • Added regression test test_original_tool_calling_raises_tool_usage_error_for_non_dict_arguments — fails with RuntimeError on the old code, passes with ToolUsageError after the fix.
  • Added test_original_tool_calling_returns_tool_usage_error_for_non_dict_without_raise covering the raise_error=False path.
  • uv run pytest lib/crewai/tests/tools/ → 135 passed.
  • uv run ruff check lib/ → clean.
  • uv run mypy lib/crewai/src/crewai/tools/tool_usage.py → clean (strict).

Fixes #6430.

Note: per .github/CONTRIBUTING.md, this PR is authored by an AI agent (Claude Code) and carries the llm-generated label.

…rguments

_original_tool_calling used a bare  outside any active except
block when the parsed arguments were not a dict. With raise_error=True
this raised  rather than
the intended ToolUsageError. Raise the explicit exception instead.

Fixes crewAIInc#6430
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Fixed a bare raise statement in ToolUsage._original_tool_calling that caused a RuntimeError when raise_error=True and tool input validation returned a non-dict value. The method now raises ToolUsageError with the existing error message. Added regression tests covering both raise_error states.

Changes

Bare raise fix and regression tests

Layer / File(s) Summary
Raise ToolUsageError instead of bare raise
lib/crewai/src/crewai/tools/tool_usage.py
When raise_error=True and validated tool input is not a dict, raises ToolUsageError(tool_arguments_error) instead of re-raising a nonexistent active exception.
Regression tests for both raise_error paths
lib/crewai/tests/tools/test_tool_usage.py
Imports ToolUsageError, adds tests asserting raise_error=True raises ToolUsageError and raise_error=False returns a ToolUsageError instance.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: replacing a bare raise with ToolUsageError for non-dict tool arguments.
Description check ✅ Passed The description is directly related to the code change and regression tests in this pull request.
Linked Issues check ✅ Passed The change matches #6430 by raising ToolUsageError with the existing message and adding regression tests for both paths.
Out of Scope Changes check ✅ Passed The PR stays focused on the bug fix and related tests, with no unrelated changes evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/crewai/tests/tools/test_tool_usage.py (1)

562-587: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting the error message in the raise_error=True test.

The PR objectives mention verifying "corrected exception type and message," but pytest.raises(ToolUsageError) only checks the type. Adding a match= parameter would also confirm the i18n tool_arguments_error message is surfaced, strengthening the regression test against future changes that might raise a different ToolUsageError.

♻️ Proposed fix to assert the error message
-            with pytest.raises(ToolUsageError):
+            with pytest.raises(ToolUsageError, match="not a valid key, value dictionary"):
                tool_usage._original_tool_calling("tool_string", raise_error=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/tools/test_tool_usage.py` around lines 562 - 587, The
regression test for ToolUsage._original_tool_calling currently only checks that
ToolUsageError is raised; update the assertion in
test_original_tool_calling_raises_tool_usage_error_for_non_dict_arguments to
also verify the exception message with pytest.raises(..., match=...) so it
confirms the i18n tool_arguments_error text is emitted when raise_error=True.
Keep the existing mock setup and patching around _select_tool and
_validate_tool_input, and strengthen the test so it validates both the exception
type and message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/tests/tools/test_tool_usage.py`:
- Around line 562-587: The regression test for ToolUsage._original_tool_calling
currently only checks that ToolUsageError is raised; update the assertion in
test_original_tool_calling_raises_tool_usage_error_for_non_dict_arguments to
also verify the exception message with pytest.raises(..., match=...) so it
confirms the i18n tool_arguments_error text is emitted when raise_error=True.
Keep the existing mock setup and patching around _select_tool and
_validate_tool_input, and strengthen the test so it validates both the exception
type and message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bec184db-fdeb-49c6-8049-6766e0a9a424

📥 Commits

Reviewing files that changed from the base of the PR and between 289686a and 0b8e831.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/tools/tool_usage.py
  • lib/crewai/tests/tools/test_tool_usage.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Bare raise in ToolUsage._original_tool_calling causes RuntimeError instead of ToolUsageError

1 participant