feat(lifecycle): add on_tool_authorize hook for per-tool authorization#2912
feat(lifecycle): add on_tool_authorize hook for per-tool authorization#2912adityasingh2400 wants to merge 2 commits intoopenai:mainfrom
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
- 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
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8479139a37
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| run_authorized = await self.hooks.on_tool_authorize( | ||
| tool_context, self.agent, func_tool | ||
| ) |
There was a problem hiding this comment.
Enforce on_tool_authorize for non-function tools
The new authorization hook is only consulted in the function-tool path (_execute_single_tool_body), so returning False does not prevent execution of other local tools. In the same module, execute_local_shell_calls, execute_shell_calls, execute_apply_patch_calls, and execute_computer_actions dispatch to tool_actions.py, where those tools call on_tool_start/on_tool_end directly without any on_tool_authorize check. This creates a bypass where callers can believe they have blocked dangerous tools (for example shell or apply_patch) but those tools still run.
Useful? React with 👍 / 👎.
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 #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