Skip to content

feat: Add crew-level before_tool_call and after_tool_call governance hooks#5890

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1779364174-governance-hook-v2
Open

feat: Add crew-level before_tool_call and after_tool_call governance hooks#5890
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1779364174-governance-hook-v2

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented May 21, 2026

Summary

Adds optional before_tool_call and after_tool_call callback parameters to the Crew class, enabling per-crew governance middleware for tool call authorization as requested in #5888.

These crew-level hooks complement the existing global hook system (register_before_tool_call_hook/register_after_tool_call_hook), scoping governance policies to individual crew instances.

before_tool_call(agent, tool_name, tool_input) — Runs before each tool execution. Raise any exception to block the call; the exception message is returned to the agent as the tool result.

after_tool_call(agent, tool_name, tool_input, tool_output) — Runs after each tool execution for audit/logging. Exceptions in this callback are caught and logged to avoid disrupting the agent loop.

Usage example

from crewai import Crew

def before_tool_call(agent, tool_name, tool_input):
    if tool_name == "customer_export" and agent.role != "Admin":
        raise PermissionError("customer_export requires Admin role")

def after_tool_call(agent, tool_name, tool_input, tool_output):
    print(f"[AUDIT] {agent.role} called {tool_name}")

crew = Crew(
    agents=[researcher, writer],
    before_tool_call=before_tool_call,
    after_tool_call=after_tool_call,
)

Changes

  • lib/crewai/src/crewai/crew.py: Added before_tool_call and after_tool_call optional SerializableCallable fields
  • lib/crewai/src/crewai/utilities/tool_utils.py: Invoke crew-level hooks in both execute_tool_and_check_finality and aexecute_tool_and_check_finality
  • lib/crewai/tests/hooks/test_tool_hooks.py: 8 new tests in TestCrewLevelToolCallHooks class

Closes #5888

Review & Testing Checklist for Human

  • Verify before_tool_call blocks tool execution when the callback raises — run a crew with a hook that raises on a specific tool and confirm the tool is never invoked
  • Verify after_tool_call receives the correct tool_output after execution
  • Confirm existing crews without hooks work identically (no regressions)
  • Test interaction between crew-level hooks and global hooks (both should fire)

Notes

Crew-level hooks run after global hooks (register_before_tool_call_hook) but before tool execution. If either blocks, the tool is not executed.

Link to Devin session: https://app.devin.ai/sessions/b76e0c6156684df4a930dfb1373c1b94

Summary by CodeRabbit

  • New Features
    • Added optional callback hooks for tool execution: before_tool_call (invoked before each tool runs with agent, tool name, and input; can block execution by raising an exception) and after_tool_call (invoked after each tool completes with the tool output).

Review Change Stack

…hooks

Adds optional before_tool_call and after_tool_call callback parameters to the
Crew class, enabling per-crew governance middleware for tool call authorization.

- before_tool_call(agent, tool_name, tool_input): Runs before each tool
  execution. Raise an exception to block the call.
- after_tool_call(agent, tool_name, tool_input, tool_output): Runs after
  each tool execution for audit/logging.

These crew-level hooks complement the existing global hook system, scoping
governance policies to individual crew instances.

Closes #5888

Co-Authored-By: João <joao@crewai.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

Added optional before_tool_call and after_tool_call callbacks to the Crew model for governance and authorization. The before-hook intercepts tool execution and can raise to block; the after-hook observes completed tool calls. Both hooks are conditionally invoked in sync and async tool execution paths with proper error handling. Comprehensive tests validate blocking, execution flow, output observation, and hook defaults.

Changes

Tool Call Hooks for Crew Governance

Layer / File(s) Summary
Crew model hook fields
lib/crewai/src/crewai/crew.py
Crew Pydantic model declares before_tool_call and after_tool_call as optional SerializableCallable fields with documented signatures for agent-based authorization and runtime tool call control.
Tool execution hook integration
lib/crewai/src/crewai/utilities/tool_utils.py
Sync and async tool execution functions conditionally invoke crew hooks: before-hook can raise to block and short-circuit to error result; after-hook logs exceptions and chains to existing global hooks.
Test suite for hook behavior
lib/crewai/tests/hooks/test_tool_hooks.py
Eight test methods validate before-hook blocking via exception, before-hook allowing execution, after-hook receiving output, blocking preventing after invocation, no-hooks path, hook call ordering, Crew field storage, and hook defaults to None.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Before and after, hooks now lead the way,
Crew calls are governed, come what may!
Authorization guards each tool's keen call,
With tests to prove it works for all! 🛡️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding crew-level before_tool_call and after_tool_call governance hooks to enable tool call authorization and governance.
Linked Issues check ✅ Passed The PR implements the governance hook feature requested in issue #5888, adding before_tool_call and after_tool_call callbacks that enable tool call authorization, blocking, and audit trails as specified.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the requested governance hooks: crew model fields, tool execution logic, and comprehensive tests. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1779364174-governance-hook-v2

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/crewai/tests/hooks/test_tool_hooks.py (1)

825-1233: ⚡ Quick win

Add async-path hook coverage.

The new crew-level tests validate only execute_tool_and_check_finality (sync). Please add at least one blocking and one post-call assertion for aexecute_tool_and_check_finality to protect the async path.

Example test shape
`@pytest.mark.asyncio`
async def test_crew_before_tool_call_blocks_execution_async():
    from crewai.utilities.tool_utils import aexecute_tool_and_check_finality
    # setup mocks + monkeypatch parse_tool_calling/ause
    # assert blocked message and no tool execution
🤖 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/hooks/test_tool_hooks.py` around lines 825 - 1233, Tests
only cover the synchronous path; add equivalent async tests that exercise the
crew-level hooks for aexecute_tool_and_check_finality: create two new
pytest.mark.asyncio tests—one where mock crew.before_tool_call raises (use
aexecute_tool_and_check_finality and monkeypatch ToolUsage.parse_tool_calling
and ToolUsage.ause) and assert the returned ToolResult contains the blocking
message and that the tool use was not called, and another where
crew.after_tool_call records the tool_output (monkeypatch parse_tool_calling and
ToolUsage.ause to return a value) and assert after_tool_call received that
output; reference functions aexecute_tool_and_check_finality,
ToolUsage.parse_tool_calling and ToolUsage.ause to locate where to patch.
🤖 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.

Inline comments:
In `@lib/crewai/src/crewai/utilities/tool_utils.py`:
- Around line 118-123: The crew hook invocation is passing the raw tool name
(tool_calling.tool_name) which can desync governance checks; change the calls to
use the canonical sanitized_tool_name instead—specifically update the
crew_before_hook/before_tool_call invocation (and any similar crew hook calls)
to pass sanitized_tool_name (the variable used for resolved/canonical names) so
hooks receive the normalized identifier; ensure return behavior (ToolResult on
exception) remains unchanged and update the other occurrences mentioned (around
the other ranges) to the same sanitized_tool_name parameter.

---

Nitpick comments:
In `@lib/crewai/tests/hooks/test_tool_hooks.py`:
- Around line 825-1233: Tests only cover the synchronous path; add equivalent
async tests that exercise the crew-level hooks for
aexecute_tool_and_check_finality: create two new pytest.mark.asyncio tests—one
where mock crew.before_tool_call raises (use aexecute_tool_and_check_finality
and monkeypatch ToolUsage.parse_tool_calling and ToolUsage.ause) and assert the
returned ToolResult contains the blocking message and that the tool use was not
called, and another where crew.after_tool_call records the tool_output
(monkeypatch parse_tool_calling and ToolUsage.ause to return a value) and assert
after_tool_call received that output; reference functions
aexecute_tool_and_check_finality, ToolUsage.parse_tool_calling and
ToolUsage.ause to locate where to patch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d33e98b6-17bd-4458-abd6-82bc9d5a7727

📥 Commits

Reviewing files that changed from the base of the PR and between 81c21e3 and 24e8ff6.

📒 Files selected for processing (3)
  • lib/crewai/src/crewai/crew.py
  • lib/crewai/src/crewai/utilities/tool_utils.py
  • lib/crewai/tests/hooks/test_tool_hooks.py

Comment on lines +118 to +123
crew_before_hook = getattr(crew, "before_tool_call", None) if crew else None
if crew_before_hook:
try:
crew_before_hook(agent, tool_calling.tool_name, tool_input)
except Exception as e:
return ToolResult(str(e), False)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use canonical tool names when invoking crew hooks.

Line 121/253 and Line 130/262 pass raw tool_calling.tool_name instead of sanitized_tool_name. This can desync governance checks from actual tool resolution and allow bypasses when names differ only by formatting/casing.

Proposed fix
-                crew_before_hook(agent, tool_calling.tool_name, tool_input)
+                crew_before_hook(agent, sanitized_tool_name, tool_input)
...
-                crew_after_hook(agent, tool_calling.tool_name, tool_input, tool_result)
+                crew_after_hook(agent, sanitized_tool_name, tool_input, tool_result)
...
-                crew_before_hook(agent, tool_calling.tool_name, tool_input)
+                crew_before_hook(agent, sanitized_tool_name, tool_input)
...
-                crew_after_hook(agent, tool_calling.tool_name, tool_input, tool_result)
+                crew_after_hook(agent, sanitized_tool_name, tool_input, tool_result)

Also applies to: 127-132, 250-255, 259-264

🤖 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/src/crewai/utilities/tool_utils.py` around lines 118 - 123, The
crew hook invocation is passing the raw tool name (tool_calling.tool_name) which
can desync governance checks; change the calls to use the canonical
sanitized_tool_name instead—specifically update the
crew_before_hook/before_tool_call invocation (and any similar crew hook calls)
to pass sanitized_tool_name (the variable used for resolved/canonical names) so
hooks receive the normalized identifier; ensure return behavior (ToolResult on
exception) remains unchanged and update the other occurrences mentioned (around
the other ranges) to the same sanitized_tool_name parameter.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]:Governance middleware hook for tool call authorization

0 participants