Skip to content

feat(lifecycle): add on_tool_authorize hook for per-tool authorization (#2868)#2

Open
adityasingh2400 wants to merge 2 commits intomainfrom
feat/tool-authorization-hook-2868
Open

feat(lifecycle): add on_tool_authorize hook for per-tool authorization (#2868)#2
adityasingh2400 wants to merge 2 commits intomainfrom
feat/tool-authorization-hook-2868

Conversation

@adityasingh2400
Copy link
Copy Markdown
Owner

@adityasingh2400 adityasingh2400 commented Apr 14, 2026

Summary

There's currently no way to programmatically block tool execution at the hook layer — you can observe calls via on_tool_start, but you can't prevent them. This PR adds on_tool_authorize to both RunHooksBase and AgentHooksBase so callers can intercept tool calls before they happen.

Closes openai#2868

What changed

  • Added on_tool_authorize(context, agent, tool) -> bool to RunHooksBase and AgentHooksBase in lifecycle.py
  • Wired the check in _execute_single_tool_body inside tool_execution.py, between input guardrails and on_tool_start
  • Both run-level and agent-level hooks are consulted; either can deny the call
  • Default returns True — fully backwards compatible

Behavior when denied

  • Tool function is never invoked
  • on_tool_start and on_tool_end are skipped for that call
  • The model receives "Tool call denied: authorization hook returned False." as the tool result

Usage example

from agents import Agent, Runner
from agents.lifecycle import RunHooks

BLOCKED_TOOLS = {"delete_file", "run_shell"}

class SafetyHooks(RunHooks):
    async def on_tool_authorize(self, context, agent, tool) -> bool:
        if tool.name in BLOCKED_TOOLS:
            print(f"Blocked tool call: {tool.name}")
            return False
        return True

agent = Agent(name="assistant", model="gpt-4o", tools=[...])
await Runner.run(agent, "do something", hooks=SafetyHooks())

Tests

Added tests/test_tool_authorize_hook.py covering:

  • Allow hook: tool runs normally, all hooks called
  • Deny hook: tool not invoked, on_tool_start/on_tool_end skipped
  • Denial message is returned to the model
  • Agent-level deny hook also works
  • Hook not called when no tool is used
  • Default base class allows everything

Summary by CodeRabbit

  • New Features

    • Added tool authorization lifecycle hook to control and deny tool execution at run and agent levels.
    • Tool calls can now be authorized or denied before execution; denied calls are skipped with a denial message.
  • Tests

    • Added comprehensive test coverage for the authorization hook behavior.

openai#2868)

Adds on_tool_authorize to both RunHooksBase and AgentHooksBase.  The hook
fires before each tool execution and can return False to block the call.

When denied:
- The tool function is never invoked
- on_tool_start and on_tool_end are skipped for that call
- The model receives 'Tool call denied: authorization hook returned False.'
  as the tool output so it can react gracefully

Both run-level and agent-level hooks are checked; either can deny the call.
The default implementation returns True (allow all), so this is fully
backwards-compatible.

Closes openai#2868
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

Warning

Rate limit exceeded

@adityasingh2400 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 49 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 49 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a322ae0e-4c54-40d9-ae0e-2af08211468b

📥 Commits

Reviewing files that changed from the base of the PR and between 5e500a6 and 529993b.

📒 Files selected for processing (1)
  • tests/test_tool_authorize_hook.py
📝 Walkthrough

Walkthrough

A new lifecycle hook on_tool_authorize has been added to the hook system, enabling authorization checks before tool execution. The hook is implemented at both run and agent levels, supporting denial of tool calls. When denied, tool execution is skipped and a denial message is returned instead of proceeding with the tool call.

Changes

Cohort / File(s) Summary
Lifecycle Hook Definitions
src/agents/lifecycle.py
Added on_tool_authorize async method to both RunHooksBase and AgentHooksBase classes, allowing authorization decisions before tool execution. Both default implementations return True.
Authorization Gate Implementation
src/agents/run_internal/tool_execution.py
Integrated two-stage authorization check in _execute_single_tool_body: validates run-level and agent-level hooks, returning denial message if either authorization fails.
Test Coverage
tests/test_tool_authorize_hook.py
Added comprehensive test suite covering authorization hook behavior: allowing/denying at run and agent levels, correct hook invocation sequences, denial message delivery, and default permissive behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Agent
    participant ToolExecution
    participant RunHooks
    participant AgentHooks
    participant Tool
    
    Agent->>ToolExecution: Execute tool
    ToolExecution->>RunHooks: on_tool_authorize()
    alt Run-level denied
        RunHooks-->>ToolExecution: False
        ToolExecution-->>Agent: Denial message
    else Run-level allowed
        RunHooks-->>ToolExecution: True
        ToolExecution->>AgentHooks: on_tool_authorize()
        alt Agent-level denied
            AgentHooks-->>ToolExecution: False
            ToolExecution-->>Agent: Denial message
        else Both allowed
            AgentHooks-->>ToolExecution: True
            ToolExecution->>RunHooks: on_tool_start()
            RunHooks-->>ToolExecution: Complete
            ToolExecution->>Tool: Invoke
            Tool-->>ToolExecution: Result
            ToolExecution->>RunHooks: on_tool_end()
            RunHooks-->>ToolExecution: Complete
            ToolExecution-->>Agent: Tool output
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A gate of approval before tools take flight,
Two hooks standing guard—run-level, agent-tight!
Authorization flows, denials ring true,
When "False" is returned, the tool cannot breakthrough. 🚫✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a new on_tool_authorize hook for per-tool authorization in the lifecycle module.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tool-authorization-hook-2868

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_tool_authorize_hook.py`:
- Around line 97-123: The test never wires my_tool_impl to func_tool nor asserts
on invoked, so it doesn't prove the tool was skipped; update the setup to create
func_tool using the actual implementation (e.g., attach my_tool_impl to the
function tool created by get_function_tool or use a builder that accepts the
implementation), ensure Runner uses that func_tool, and add an assertion like
assert invoked == [] after Runner.run to verify the implementation was not
called; reference my_tool_impl, invoked, func_tool, get_function_tool,
Runner.run, and hooks to locate and modify the test.
- Around line 126-150: The test test_deny_hook_sends_denial_message_to_model
should explicitly assert that the denial payload (DENIAL_MSG) was forwarded to
the model when OutputCapturingHooks.on_tool_authorize returns False; update the
assertions after Runner.run to inspect result.raw_responses (or the model's
captured inputs) and assert that the second turn's input includes a tool
output/text equal to DENIAL_MSG, in addition to keeping the final_output ==
"done".
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0792dcde-b55c-4824-a61f-fbe9eb2a0c71

📥 Commits

Reviewing files that changed from the base of the PR and between 5c9fb2c and 5e500a6.

📒 Files selected for processing (3)
  • src/agents/lifecycle.py
  • src/agents/run_internal/tool_execution.py
  • tests/test_tool_authorize_hook.py

Comment thread tests/test_tool_authorize_hook.py
Comment thread tests/test_tool_authorize_hook.py
- Wire my_tool_impl directly into FunctionTool so test_deny_hook_skips_tool_execution
  actually verifies tool impl was not called; add assert invoked == []
- Add explicit DENIAL_MSG assertion in test_deny_hook_sends_denial_message_to_model
  by checking new_items for ToolCallOutputItem with denial string
- Add type annotation for invoked: list[bool]
- Add missing docstrings to all hook classes and methods
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.

Per-tool authorization middleware for agent tool calls

1 participant