Behaviour for implementing pluggable approval hooks.
Hooks can provide synchronous or asynchronous approval decisions for tool invocations, command executions, and file access operations.
Callbacks
prepare/2- Called before the approval review, can mutate metadatareview_tool/3- Review a tool invocationreview_command/3- Review a command execution (optional)review_file/3- Review a file access operation (optional)await/2- Wait for an async approval decision (optional)
Return Values
Synchronous hooks return:
:allow- approve the operation{:allow, opts}- approve with additional options (used by app-server approvals){:deny, reason}- deny with a reason string
Asynchronous hooks return:
{:async, ref}- defer decision, will callawait/2later{:async, ref, metadata}- defer decision with additional metadata
{:allow, opts} supports :grant_root to grant file-change approvals for the
session when requested by the app-server.
Example
defmodule MyApp.SlackApprovalHook do
@behaviour Codex.Approvals.Hook
@impl true
def prepare(event, context) do
# Add custom metadata before review
{:ok, Map.put(context, :slack_channel, "#approvals")}
end
@impl true
def review_tool(event, context, _opts) do
# Post to Slack and return async ref
ref = make_ref()
MyApp.SlackClient.post_approval_request(ref, event, context)
{:async, ref}
end
@impl true
def await(ref, timeout) do
# Wait for Slack response
receive do
{:approval_decision, ^ref, decision} -> {:ok, decision}
after
timeout -> {:error, :timeout}
end
end
end
Summary
Callbacks
Wait for an async approval decision.
Called before any review operation to prepare or augment context.
Review a command execution request (optional).
Review a file access request (optional).
Review a tool invocation request.
Functions
Default prepare implementation that returns the context unchanged.
Default review implementation that allows all operations.
Types
@type async_ref() :: reference()
@type context() :: map()
@type decision() :: :allow | {:allow, allow_opts()} | {:deny, String.t() | atom()}
@type event() :: map()
@type opts() :: keyword()
@type review_result() :: decision() | async_result()
Callbacks
@callback await(async_ref(), timeout :: pos_integer()) :: {:ok, decision()} | {:error, :timeout | term()}
Wait for an async approval decision.
This callback is called when a review returned {:async, ref} and the
system needs to wait for the decision.
Parameters
ref- The reference returned by the review callbacktimeout- Maximum time to wait in milliseconds
Returns
{:ok, decision}- the approval decision{:error, :timeout}- timeout reached{:error, reason}- other error
Called before any review operation to prepare or augment context.
This callback can be used to add metadata, initialize state, or transform the context before it's passed to review callbacks.
@callback review_command(event(), context(), opts()) :: review_result()
Review a command execution request (optional).
If not implemented, commands are allowed by default.
@callback review_file(event(), context(), opts()) :: review_result()
Review a file access request (optional).
If not implemented, file operations are allowed by default.
@callback review_tool(event(), context(), opts()) :: review_result()
Review a tool invocation request.
Parameters
event- The tool call event (contains tool_name, arguments, call_id, etc.)context- The approval context (thread, metadata, etc.)opts- Hook-specific options
Returns
:allow- approve the tool invocation{:deny, reason}- deny with a reason{:async, ref}- defer decision, will be awaited later{:async, ref, metadata}- defer with additional metadata