feat(lifecycle): add on_tool_authorize hook for per-tool authorization (#2868)#2
feat(lifecycle): add on_tool_authorize hook for per-tool authorization (#2868)#2adityasingh2400 wants to merge 2 commits intomainfrom
Conversation
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
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughA new lifecycle hook Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
src/agents/lifecycle.pysrc/agents/run_internal/tool_execution.pytests/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
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 addson_tool_authorizeto bothRunHooksBaseandAgentHooksBaseso callers can intercept tool calls before they happen.Closes openai#2868
What changed
on_tool_authorize(context, agent, tool) -> booltoRunHooksBaseandAgentHooksBaseinlifecycle.py_execute_single_tool_bodyinsidetool_execution.py, between input guardrails andon_tool_startTrue— fully backwards compatibleBehavior when denied
on_tool_startandon_tool_endare skipped for that call"Tool call denied: authorization hook returned False."as the tool resultUsage example
Tests
Added
tests/test_tool_authorize_hook.pycovering:on_tool_start/on_tool_endskippedSummary by CodeRabbit
New Features
Tests