Skip to content

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

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

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

Conversation

@ashusnapx

@ashusnapx ashusnapx commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Fixes RuntimeError: No active exception to re-raise in ToolUsage._original_tool_calling() when _validate_tool_input returns a non-dict and raise_error=True.

Closes #6430

Problem

_original_tool_calling() at tool_usage.py:853 had a bare raise outside any except block:

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

When this path was hit with raise_error=True, Python raised RuntimeError: No active exception to re-raise instead of the intended ToolUsageError. The _tool_calling() fallback relies on catching a meaningful exception to trigger its retry logic, so this produced a confusing RuntimeError.

Also flagged by ruff check --select PLE0704.

Fix

Replace bare raise with explicit raise ToolUsageError(...):

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')}")

The except block above (line 846-848) correctly uses bare raise inside an active exception context — left unchanged.

Tests (4 new)

Test What it verifies
test_original_tool_calling_non_dict_raises_tool_usage_error raise_error=True raises ToolUsageError, not RuntimeError
test_original_tool_calling_non_dict_returns_error raise_error=False returns ToolUsageError
test_tool_calling_fallback_on_non_dict Full _tool_calling flow falls back gracefully
test_non_dict_raises_not_runtime_error Exception type is ToolUsageError, not RuntimeError

All 32 tests in test_tool_usage.py pass. ruff check --select PLE0704 clean.

Verification

pytest lib/crewai/tests/tools/test_tool_usage.py -v
ruff check lib/crewai/src/crewai/tools/tool_usage.py --select PLE0704

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0897968b-51dc-485c-a47b-1ed42a99e3fe

📥 Commits

Reviewing files that changed from the base of the PR and between a395ea2 and ae7ebf9.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/tools/tool_usage.py
  • lib/crewai/tests/tools/test_tool_usage.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/crewai/src/crewai/tools/tool_usage.py
  • lib/crewai/tests/tools/test_tool_usage.py

📝 Walkthrough

Walkthrough

ToolUsage._original_tool_calling now consistently raises or returns a localized ToolUsageError for tool-argument validation failures and non-dict parsed input. Regression tests cover direct calls, fallback behavior, and prevention of RuntimeError.

Changes

Bare Raise Fix

Layer / File(s) Summary
Raise ToolUsageError for invalid arguments
lib/crewai/src/crewai/tools/tool_usage.py
_original_tool_calling reuses a localized ToolUsageError for validation failures and non-dict parsed input, raising or returning it according to raise_error.
Regression tests for non-dict input
lib/crewai/tests/tools/test_tool_usage.py
Adds a minimal ToolUsage helper and tests for raised, returned, and fallback ToolUsageError behavior instead of RuntimeError.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes the fix for non-dict tool arguments and is specific to the main code change.
Description check ✅ Passed The description matches the code change and regression tests for the ToolUsage error path.
Linked Issues check ✅ Passed The PR addresses #6430 by replacing the bare raise, preserving fallback behavior, and adding regression tests.
Out of Scope Changes check ✅ Passed The changes are limited to the targeted bug fix in tool_usage.py and matching tests, with no unrelated additions.
✨ 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.

@ErenAta16 ErenAta16 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.

Same one-line fix as #6486, #6512, #6511, #6499 — this is one of five open PRs (added this one to the tally) independently fixing the same bare-raise-outside-except bug in _original_tool_calling. Reproduced the bug standalone: the bare raise here raises RuntimeError: No active exception to reraise rather than the intended ToolUsageError, since it is outside any except block. This PR's fix is correct and equivalent to the others. Left a fuller comparison on #6486, which constructs the error once and reuses it for both branches rather than twice — noting the duplication here so only one lands.

…rguments

Closes crewAIInc#6430

_original_tool_calling() contained a bare `raise` on the non-dict
arguments path (line 853) outside any except block. When
_validate_tool_input returned a non-dict and raise_error=True, this
produced RuntimeError: No active exception to re-raise instead of the
intended ToolUsageError.

Replace with explicit `raise ToolUsageError(...)`. The except block
above (line 846-848) correctly uses bare `raise` inside an active
exception context — left unchanged.

Also resolves ruff PLE0704 (bare raise not inside exception handler).

Tests: 4 new regression tests covering the non-dict branch with both
raise_error settings, the _tool_calling fallback flow, and verification
that RuntimeError is never raised. All 32 tests in test_tool_usage.py
pass. ruff clean.
@ashusnapx ashusnapx force-pushed the fix/tool-usage-bare-raise branch from a395ea2 to ae7ebf9 Compare July 11, 2026 16:50
@ashusnapx

Copy link
Copy Markdown
Author

Fixed — error is now constructed once and reused for both branches (raise and return), matching the approach in #6486. Thanks for the catch.

@ErenAta16

Copy link
Copy Markdown

Confirmed - the current diff constructs tool_arguments_error once before the try block and both the raise and the return paths reuse the same instance now. That's the same shape as #6486, so this fix is correct.

@ashusnapx

Copy link
Copy Markdown
Author

Thanks for confirming. Ready for maintainer review.

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

2 participants