# `Codex.Approvals.Hook`
[🔗](https://github.com/nshkrdotcom/codex_sdk/blob/v0.18.0/lib/codex/approvals/hook.ex#L1)

Behaviour for implementing pluggable approval hooks.

Hooks can provide synchronous or asynchronous approval decisions for tool invocations,
command executions, file access operations, and request-permissions prompts.

## Callbacks

- `c:prepare/2` - Called before the approval review, can mutate metadata
- `c:review_tool/3` - Review a tool invocation
- `c:review_command/3` - Review a command execution (optional)
- `c:review_file/3` - Review a file access operation (optional)
- `c:review_permissions/3` - Review a request-permissions approval prompt (optional)
- `c: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 call `c:await/2` later
- `{: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. Permissions approvals additionally
support `:permissions` and `:scope`.

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

# `allow_opts`

```elixir
@type allow_opts() :: [
  for_session: boolean(),
  execpolicy_amendment: [String.t()],
  grant_root: String.t() | Path.t(),
  permissions:
    Codex.Protocol.RequestPermissions.RequestPermissionProfile.t()
    | Codex.Protocol.RequestPermissions.GrantedPermissionProfile.t()
    | map(),
  scope: Codex.Protocol.RequestPermissions.PermissionGrantScope.t()
]
```

# `async_ref`

```elixir
@type async_ref() :: reference()
```

# `async_result`

```elixir
@type async_result() ::
  {:async, async_ref()} | {:async, async_ref(), metadata :: map()}
```

# `context`

```elixir
@type context() :: map()
```

# `decision`

```elixir
@type decision() :: :allow | {:allow, allow_opts()} | {:deny, String.t() | atom()}
```

# `event`

```elixir
@type event() :: map()
```

# `opts`

```elixir
@type opts() :: keyword()
```

# `review_result`

```elixir
@type review_result() :: decision() | async_result()
```

# `await`
*optional* 

```elixir
@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 callback
- `timeout` - Maximum time to wait in milliseconds

## Returns
- `{:ok, decision}` - the approval decision
- `{:error, :timeout}` - timeout reached
- `{:error, reason}` - other error

# `prepare`
*optional* 

```elixir
@callback prepare(event(), context()) :: {:ok, context()} | {:error, term()}
```

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.

# `review_command`
*optional* 

```elixir
@callback review_command(event(), context(), opts()) :: review_result()
```

Review a command execution request (optional).

App-server command approval events include normalized snake_case fields such as
`:approval_id`, `:command`, `:cwd`, `:command_actions`,
`:network_approval_context`, `:additional_permissions`,
`:proposed_execpolicy_amendment`, `:proposed_network_policy_amendments`, and
`:available_decisions` when the connected Codex build sends them.

If not implemented, commands are allowed by default.

# `review_file`
*optional* 

```elixir
@callback review_file(event(), context(), opts()) :: review_result()
```

Review a file access request (optional).

App-server file approval events include normalized `:grant_root` when the
server suggests a session-scoped writable root.

If not implemented, file operations are allowed by default.

# `review_permissions`
*optional* 

```elixir
@callback review_permissions(event(), context(), opts()) :: review_result()
```

Review a request-permissions approval request (optional).

App-server permissions approval events expose a structured
`Codex.Protocol.RequestPermissions.RequestPermissionProfile`, including
network, filesystem, and macOS permission families when upstream provides
them.

If not implemented, permissions requests are allowed by default.

# `review_tool`

```elixir
@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

# `default_prepare`

Default prepare implementation that returns the context unchanged.

# `default_review`

Default review implementation that allows all operations.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
