Packages
claude_code
0.36.0
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.1
0.32.2
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Claude Agent SDK for Elixir – Build AI agents with Claude Code
Current section
Files
Jump to
Current section
Files
lib/claude_code/hook.ex
defmodule ClaudeCode.Hook do
@moduledoc """
Behaviour for hook callbacks.
Implement this behaviour in a module, or pass an anonymous function
with the same `call/2` signature. Used by the `:hooks` option.
See the [Hooks guide](hooks.html) for configuration, matchers, patterns,
and troubleshooting.
## Return values
All hooks can return `:ok` (no opinion / acknowledge) or a tagged tuple
`{action, opts}` where `opts` is a keyword list of struct field names.
### Universal actions
These work for any hook event. The `opts` correspond to
`ClaudeCode.Hook.Output` fields.
| Return | Effect |
|--------|--------|
| `:ok` | No opinion / acknowledge |
| `{:halt, opts}` | Stop the agent from continuing (e.g. `stop_reason: "Budget exhausted"`) |
| `{:block, opts}` | Block the current action (e.g. `reason: "Rate limited"`) |
### Event-specific actions
These are only valid for certain events. The `opts` correspond to
the event's output struct — see each event below for which actions
and fields apply.
| Return | Events |
|--------|--------|
| `{:ok, opts}` | Most events — acknowledge with event-specific data |
| `{:allow, opts}` | `PreToolUse`, `PermissionRequest` |
| `{:deny, opts}` | `PreToolUse`, `PermissionRequest` |
| `{:ask, opts}` | `PreToolUse` only |
### Full struct (escape hatch)
%Output{suppress_output: true, hook_specific_output: %Output.PreToolUse{...}}
For when you need top-level fields alongside hook-specific data.
See `ClaudeCode.Hook.Output` for the wrapper struct fields.
### Async responses
Return `%ClaudeCode.Hook.Output.Async{}` to signal that the hook will
respond asynchronously — the CLI proceeds without waiting.
## Common input fields
All hook events include these base fields:
| Field | Type | Description |
|-------|------|-------------|
| `:hook_event_name` | `String.t()` | The hook type (`"PreToolUse"`, `"PostToolUse"`, etc.) |
| `:session_id` | `String.t()` | Current session identifier |
| `:transcript_path` | `String.t()` | Path to the conversation transcript |
| `:cwd` | `String.t()` | Current working directory |
| `:permission_mode` | `String.t()` | Permission mode (e.g., `"default"`, `"acceptEdits"`, `"bypassPermissions"`) |
| `:agent_id` | `String.t()` | Subagent identifier. Present only within a subagent; absent on the main thread. |
| `:agent_type` | `String.t()` | Agent type name (e.g., `"general-purpose"`, `"code-reviewer"`). Present within subagents, or on the main thread when started with `--agent`. |
> **Key normalization:** Hook input fields are converted to atom keys. All documented
> fields are guaranteed to be atoms. Unknown or future fields fall back to
> `String.to_existing_atom/1` — if the atom doesn't already exist at runtime, the key
> is preserved as a string, avoiding unbounded atom creation.
## Hook event reference
### PreToolUse
Fires before a tool executes. Can block, allow, or modify the tool call.
Output struct: `ClaudeCode.Hook.Output.PreToolUse`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:tool_name` | `String.t()` | Name of the tool being called |
| `:tool_input` | `map` | Arguments passed to the tool (keys are strings) |
| `:tool_use_id` | `String.t()` | Unique identifier for this tool call |
**Return values:** `:ok`, `{:ok, opts}`, `{:allow, opts}`, `{:deny, opts}`, or `{:ask, opts}`
where opts are `ClaudeCode.Hook.Output.PreToolUse` fields.
**Example:**
defmodule MyApp.BlockDangerous do
@behaviour ClaudeCode.Hook
@impl true
def call(%{hook_event_name: "PreToolUse", tool_input: %{"command" => cmd}}, _tool_use_id) do
if String.contains?(cmd, "rm -rf /") do
{:deny, permission_decision_reason: "Dangerous command blocked"}
else
{:allow, []}
end
end
def call(_input, _tool_use_id), do: :ok
end
### PostToolUse
Fires after a tool executes successfully. Observation only.
Output struct: `ClaudeCode.Hook.Output.PostToolUse`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:tool_name` | `String.t()` | Name of the tool that was called |
| `:tool_input` | `map` | Arguments that were passed to the tool |
| `:tool_response` | `any` | Result returned from tool execution |
| `:tool_use_id` | `String.t()` | Unique identifier for this tool call |
**Return values:** `:ok` or `{:ok, opts}` where opts are
`ClaudeCode.Hook.Output.PostToolUse` fields.
**Example:**
defmodule MyApp.AuditLogger do
@behaviour ClaudeCode.Hook
@impl true
def call(%{hook_event_name: "PostToolUse"} = event, _tool_use_id) do
MyApp.AuditLog.insert(%{
tool: event.tool_name,
input: event.tool_input,
result: event.tool_response
})
:ok
end
def call(_input, _tool_use_id), do: :ok
end
### PostToolUseFailure
Fires after a tool execution fails. Observation only.
Output struct: `ClaudeCode.Hook.Output.PostToolUseFailure`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:tool_name` | `String.t()` | Name of the tool that failed |
| `:tool_input` | `map` | Arguments that were passed to the tool |
| `:tool_use_id` | `String.t()` | Unique identifier for this tool call |
| `:error` | `String.t()` | Error message from the failure |
| `:is_interrupt` | `boolean` | Whether the failure was caused by an interrupt |
**Return values:** `:ok` or `{:ok, opts}` where opts are
`ClaudeCode.Hook.Output.PostToolUseFailure` fields.
### UserPromptSubmit
Fires when a user submits a prompt. Can block the submission.
Output struct: `ClaudeCode.Hook.Output.UserPromptSubmit`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:prompt` | `String.t()` | The user's prompt text |
**Return values:** `:ok` or `{:block, opts}` where opts are
`ClaudeCode.Hook.Output` fields (e.g. `reason: "..."`).
Also supports `{:ok, opts}` with `ClaudeCode.Hook.Output.UserPromptSubmit` fields.
### Stop
Fires when the agent is about to stop. Can keep the session running.
Uses top-level `ClaudeCode.Hook.Output` fields only (no event-specific struct).
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:stop_hook_active` | `boolean` | Whether a stop hook is currently processing |
| `:last_assistant_message` | `String.t()` | Text content of the last assistant message before stopping |
**Return values:** `:ok` or `{:halt, opts}` where opts are
`ClaudeCode.Hook.Output` fields (e.g. `stop_reason: "..."`).
**Example:**
defmodule MyApp.BudgetGuard do
@behaviour ClaudeCode.Hook
@impl true
def call(%{hook_event_name: "Stop"}, _tool_use_id) do
if MyApp.Budget.remaining() > 0 do
{:halt, stop_reason: "Budget remaining, keep working"}
else
:ok
end
end
def call(_input, _tool_use_id), do: :ok
end
### SubagentStart
Fires when a subagent is initialized. Observation only.
Output struct: `ClaudeCode.Hook.Output.SubagentStart`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:agent_id` | `String.t()` | Unique identifier for the subagent |
| `:agent_type` | `String.t()` | Type/role of the subagent |
**Return values:** `:ok` or `{:ok, opts}` where opts are
`ClaudeCode.Hook.Output.SubagentStart` fields.
### SubagentStop
Fires when a subagent completes. Can keep the subagent running.
Uses top-level `ClaudeCode.Hook.Output` fields only (no event-specific struct).
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:stop_hook_active` | `boolean` | Whether a stop hook is currently processing |
| `:agent_id` | `String.t()` | Unique identifier for the subagent |
| `:agent_transcript_path` | `String.t()` | Path to the subagent's conversation transcript |
| `:agent_type` | `String.t()` | Type/role of the subagent |
| `:last_assistant_message` | `String.t()` | Text content of the last assistant message before stopping |
**Return values:** `:ok` or `{:halt, opts}` where opts are
`ClaudeCode.Hook.Output` fields (e.g. `stop_reason: "..."`).
### PreCompact
Fires before conversation compaction. Can provide custom instructions.
Output struct: `ClaudeCode.Hook.Output.PreCompact`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:trigger` | `String.t()` | What triggered compaction: `"manual"` or `"auto"` |
| `:custom_instructions` | `String.t() \\| nil` | Custom instructions already provided for compaction |
**Return values:** `:ok` or `{:ok, opts}` where opts are
`ClaudeCode.Hook.Output.PreCompact` fields.
### Notification
Fires when the agent sends status messages. Observation only.
Output struct: `ClaudeCode.Hook.Output.Notification`
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:message` | `String.t()` | Status message from the agent |
| `:notification_type` | `String.t()` | Type of notification: `"permission_prompt"`, `"idle_prompt"`, `"auth_success"`, or `"elicitation_dialog"` |
| `:title` | `String.t()` | Optional title set by the agent |
**Return values:** `:ok` or `{:ok, opts}` where opts are
`ClaudeCode.Hook.Output.Notification` fields.
### PermissionRequest
Fires when a tool requires permission. Similar to `PreToolUse` but occurs at the
permission prompt stage rather than the hook stage.
Output struct: `ClaudeCode.Hook.Output.PermissionRequest`
Permission decisions use `ClaudeCode.Hook.PermissionDecision.Allow` and
`ClaudeCode.Hook.PermissionDecision.Deny`.
**Input fields** (in addition to common fields):
| Field | Type | Description |
|-------|------|-------------|
| `:tool_name` | `String.t()` | Name of the tool requesting permission |
| `:tool_input` | `map` | Arguments passed to the tool |
| `:permission_suggestions` | `list` | Suggested permission updates to avoid future prompts |
**Return values:** `:ok`, `{:allow, opts}`, or `{:deny, opts}`
where opts are `ClaudeCode.Hook.PermissionDecision.Allow` or
`ClaudeCode.Hook.PermissionDecision.Deny` fields respectively.
"""
@type hook_action :: :allow | :deny | :ask | :halt | :block | :ok
@type hook_result ::
:ok
| :allow
| :deny
| {hook_action(), keyword()}
| ClaudeCode.Hook.Output.t()
| ClaudeCode.Hook.PermissionDecision.Allow.t()
| ClaudeCode.Hook.PermissionDecision.Deny.t()
@callback call(input :: map(), tool_use_id :: String.t() | nil) :: hook_result()
@doc """
Invokes a hook callback (module or function) with error protection.
Returns the callback's result, or `{:error, reason}` if it raises.
"""
@spec invoke(module() | function(), map(), String.t() | nil) :: term()
def invoke(hook, input, tool_use_id) when is_atom(hook) do
hook.call(input, tool_use_id)
rescue
e -> {:error, Exception.message(e)}
end
def invoke(hook, input, tool_use_id) when is_function(hook, 2) do
hook.(input, tool_use_id)
rescue
e -> {:error, Exception.message(e)}
end
end