Skip to content

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

Open
adityasingh2400 wants to merge 2 commits intoopenai:mainfrom
adityasingh2400:feat/tool-authorization-hook-2868
Open

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

Conversation

@adityasingh2400
Copy link
Copy Markdown
Contributor

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 #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.

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
@github-actions github-actions bot added enhancement New feature or request feature:core labels Apr 16, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +1596 to +1598
run_authorized = await self.hooks.on_tool_authorize(
tool_context, self.agent, func_tool
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

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

Labels

enhancement New feature or request feature:core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-tool authorization middleware for agent tool calls

1 participant