豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
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 a tool authorization lifecycle hook to allow or deny tool calls at both run and agent levels.
    • Denied tool calls are skipped and surface a denial message instead of invoking the tool.
  • Tests

    • Added comprehensive tests validating allow/deny behavior, that skipped tools do not run, and normal runs remain unaffected.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

Added an async lifecycle hook on_tool_authorize at run and agent levels; tool execution now awaits both hooks and skips invocation (returning a denial message) if either returns False.

Changes

Cohort / File(s) Summary
Lifecycle Hook Definitions
src/agents/lifecycle.py
Added async def on_tool_authorize(self, context: RunContextWrapper[TContext], agent: TAgent, tool: Tool) -> bool to RunHooksBase and AgentHooksBase with default True.
Authorization Gate Implementation
src/agents/run_internal/tool_execution.py
In _execute_single_tool_body, added sequential run-level then agent-level on_tool_authorize checks; if either returns False, skip on_tool_start/on_tool_end and return a denial message.
Test Coverage
tests/test_tool_authorize_hook.py
New tests covering allow/deny behavior at run and agent levels, asserting hook call sequences, denial message presence, and default permissive behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ToolExecution
    participant RunHooks
    participant AgentHooks
    participant Tool

    Client->>ToolExecution: request tool execution
    ToolExecution->>RunHooks: on_tool_authorize(context, agent, tool)
    alt run denied
        RunHooks-->>ToolExecution: False
        ToolExecution-->>Client: "Tool call denied: authorization hook returned False."
    else run allowed
        RunHooks-->>ToolExecution: True
        ToolExecution->>AgentHooks: on_tool_authorize(context, agent, tool)
        alt agent denied
            AgentHooks-->>ToolExecution: False
            ToolExecution-->>Client: "Tool call denied: authorization hook returned False."
        else agent allowed
            AgentHooks-->>ToolExecution: True
            ToolExecution->>RunHooks: on_tool_start(...)
            RunHooks-->>ToolExecution: started
            ToolExecution->>Tool: invoke(...)
            Tool-->>ToolExecution: result
            ToolExecution->>RunHooks: on_tool_end(...)
            RunHooks-->>ToolExecution: ended
            ToolExecution-->>Client: tool result
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Before the tool may spring or trot,
Two little checks decide the plot.
If both say "yes" the tool will play,
If one says "no" it stays away—
Hops and logs, a tidy spot.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 primary change: adding a new on_tool_authorize hook for authorization purposes. It accurately reflects the main addition in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 96.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 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
adityasingh2400 pushed a commit that referenced this pull request Apr 14, 2026
- 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
Ubuntu and others added 2 commits April 16, 2026 16:08
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
- 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
@adityasingh2400 adityasingh2400 force-pushed the feat/tool-authorization-hook-2868 branch from 529993b to 8479139 Compare April 16, 2026 16:08
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.

🧹 Nitpick comments (1)
src/agents/run_internal/tool_execution.py (1)

1605-1605: Extract denial text into a shared constant.

Line 1605 hardcodes a user-visible message. Consider centralizing it as a module-level constant to avoid drift across execution logic/tests/docs.

♻️ Proposed refactor
@@
 REDACTED_TOOL_ERROR_MESSAGE = "Tool execution failed. Error details are redacted."
+TOOL_AUTHORIZATION_DENIED_MESSAGE = "Tool call denied: authorization hook returned False."
@@
-        if not run_authorized or not agent_authorized:
-            return "Tool call denied: authorization hook returned False."
+        if not run_authorized or not agent_authorized:
+            return TOOL_AUTHORIZATION_DENIED_MESSAGE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agents/run_internal/tool_execution.py` at line 1605, Introduce a
module-level constant (e.g., TOOL_CALL_DENIED_MSG = "Tool call denied:
authorization hook returned False.") at the top of
src/agents/run_internal/tool_execution.py and replace the hardcoded return
string inside the function that currently returns "Tool call denied:
authorization hook returned False." with that constant; update any other
occurrences to use the same constant and run tests to ensure no string-drift
issues in execution logic or tests/docs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/agents/run_internal/tool_execution.py`:
- Line 1605: Introduce a module-level constant (e.g., TOOL_CALL_DENIED_MSG =
"Tool call denied: authorization hook returned False.") at the top of
src/agents/run_internal/tool_execution.py and replace the hardcoded return
string inside the function that currently returns "Tool call denied:
authorization hook returned False." with that constant; update any other
occurrences to use the same constant and run tests to ensure no string-drift
issues in execution logic or tests/docs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f15acc6e-ca20-4cdf-9a93-5f40a1505e5e

📥 Commits

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

📒 Files selected for processing (3)
  • src/agents/lifecycle.py
  • src/agents/run_internal/tool_execution.py
  • tests/test_tool_authorize_hook.py
✅ Files skipped from review due to trivial changes (1)
  • tests/test_tool_authorize_hook.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.

Per-tool authorization middleware for agent tool calls

1 participant