Hook output structure and helpers.
Represents the return value from hook callbacks. Hook output controls:
- Permission decisions (PreToolUse): allow, deny, or ask
- Additional context (PostToolUse, UserPromptSubmit): inject information for Claude
- Execution control: continue or stop the agent
- User feedback: system messages and reasons
Output Fields
continue- Whether to continue execution (boolean)stopReason- Message when stopping (string)suppressOutput- Hide from transcript (boolean)systemMessage- User-visible message (string)reason- Claude-visible feedback (string)decision- "block" for some events (string)hookSpecificOutput- Event-specific control (map)
Examples
# Allow a tool
Output.allow("Security check passed")
# Deny a tool
Output.deny("Dangerous command detected")
# Add context after tool execution
Output.add_context("PostToolUse", "Command completed in 2.3s")
# Stop execution
Output.stop("Critical error occurred")
# Combine helpers
Output.deny("Invalid file path")
|> Output.with_system_message("File access restricted")
|> Output.with_reason("Path outside allowed directory")See: https://docs.anthropic.com/en/docs/claude-code/hooks#hook-output
Summary
Types
Composable hook-specific output used by helper pipelines.
Hook-specific output for different event types.
Permission decision for PreToolUse hooks.
PermissionRequest hook-specific output.
PostToolUse hook-specific output.
PreToolUse hook-specific output.
SessionStart hook-specific output.
Complete hook output map.
UserPromptSubmit hook-specific output.
Functions
Creates hook output to add context.
Creates hook output to allow a PreToolUse.
Creates hook output to ask the user for permission.
Marks hook output for asynchronous processing.
Creates hook output to block with decision field.
Creates hook output to continue execution.
Creates hook output to deny a PreToolUse.
Creates a PermissionRequest hook output that allows the tool.
Creates a PermissionRequest hook output with a permission decision.
Creates a PermissionRequest hook output that denies the tool.
Creates hook output to stop execution.
Marks output to be suppressed from transcript.
Converts Elixir output to JSON-compatible map for CLI.
Validates hook output structure.
Adds additional context to hook output.
Sets the timeout for async hook processing.
Adds a reason to hook output.
Adds a system message to hook output.
Modifies tool input before execution (PreToolUse hooks only).
Sets updated MCP tool output in hook output.
Types
@type composable_hook_specific_output() :: %{ optional(:hookEventName) => String.t(), optional(:permissionDecision) => String.t() | map(), optional(:permissionDecisionReason) => String.t(), optional(:additionalContext) => String.t(), optional(:updatedInput) => map(), optional(:updatedMCPToolOutput) => term(), optional(:decision) => map(), optional(atom()) => term() }
Composable hook-specific output used by helper pipelines.
Helpers such as with_additional_context/2, with_updated_input/2,
and with_updated_mcp_output/2 can be chained onto outputs that do not
yet have a fully event-qualified hookSpecificOutput map.
@type hook_specific_output() :: pre_tool_use_output() | post_tool_use_output() | user_prompt_submit_output() | session_start_output() | permission_request_output() | composable_hook_specific_output()
Hook-specific output for different event types.
Includes both:
- Event-specific complete shapes (e.g. PreToolUse, PermissionRequest)
- Composable/partial shapes produced by helper pipelines
@type permission_decision() :: :allow | :deny | :ask
Permission decision for PreToolUse hooks.
PermissionRequest hook-specific output.
Controls permission dialogs programmatically:
hookEventName- Must be "PermissionRequest"decision- Permission decision map (e.g.%{"type" => "allow"})
PostToolUse hook-specific output.
Adds context for Claude to consider:
hookEventName- Must be "PostToolUse"additionalContext- Information about tool execution
@type pre_tool_use_output() :: %{ :hookEventName => String.t(), :permissionDecision => String.t(), :permissionDecisionReason => String.t(), optional(:updatedInput) => map() }
PreToolUse hook-specific output.
Controls whether a tool call proceeds:
hookEventName- Must be "PreToolUse"permissionDecision- "allow", "deny", or "ask"permissionDecisionReason- Explanation for the decisionupdatedInput- Optional modified tool input (viawith_updated_input/2)
SessionStart hook-specific output.
Adds context when session starts:
hookEventName- Must be "SessionStart"additionalContext- Initial context for session
@type t() :: %{ optional(:continue) => boolean(), optional(:stopReason) => String.t(), optional(:suppressOutput) => boolean(), optional(:systemMessage) => String.t(), optional(:reason) => String.t(), optional(:decision) => String.t(), optional(:hookSpecificOutput) => hook_specific_output(), optional(atom()) => term() }
Complete hook output map.
All fields are optional. The CLI processes these fields to control behavior.
UserPromptSubmit hook-specific output.
Adds context before processing prompt:
hookEventName- Must be "UserPromptSubmit"additionalContext- Contextual information to inject
Functions
Creates hook output to add context.
Used with PostToolUse, UserPromptSubmit, or SessionStart hooks.
Parameters
event_name- Hook event name ("PostToolUse", "UserPromptSubmit", etc.)context- Contextual information to inject
Examples
Output.add_context("PostToolUse", "Command took 2.3 seconds")
Output.add_context("UserPromptSubmit", "Current time: 10:00 AM")
Output.add_context("SessionStart", "Recent issues: #123, #124")
Creates hook output to allow a PreToolUse.
Parameters
reason- Explanation for allowing (default: "Approved")
Examples
Output.allow()
# => %{hookSpecificOutput: %{hookEventName: "PreToolUse", permissionDecision: "allow", ...}}
Output.allow("Security scan passed")
Creates hook output to ask the user for permission.
The CLI will prompt the user to confirm the tool use.
Parameters
reason- Explanation for asking user (required)
Examples
Output.ask("Confirm deletion of 100 files")
Output.ask("Review this API call before executing")
Marks hook output for asynchronous processing.
When async: true is set, the hook callback can continue processing
in the background while Claude continues execution. This is useful for
hooks that perform slow operations (e.g., external API calls, logging).
Parameters
output- Existing hook output
Examples
# Basic async output
Output.async(%{continue: true})
# Combined with allow
Output.allow("Approved")
|> Output.async()
# With timeout
Output.allow("Starting background check")
|> Output.async()
|> Output.with_async_timeout(30_000)
Creates hook output to block with decision field.
Used for certain hooks to provide feedback to Claude.
Parameters
reason- Explanation for blocking
Examples
Output.block("Tool execution failed validation")
@spec continue() :: t()
Creates hook output to continue execution.
Examples
Output.continue()
# => %{continue: true}
Creates hook output to deny a PreToolUse.
Parameters
reason- Explanation for denying (required)
Examples
Output.deny("Dangerous command detected")
Output.deny("File path not allowed")
@spec permission_allow() :: t()
Creates a PermissionRequest hook output that allows the tool.
Shorthand for permission_decision(Permission.Result.allow()).
Examples
Output.permission_allow()
@spec permission_decision(ClaudeAgentSDK.Permission.Result.t() | map()) :: t()
Creates a PermissionRequest hook output with a permission decision.
Used with PermissionRequest hooks to programmatically respond to permission dialogs without user interaction.
Accepts either a PermissionResult struct (converted to the CLI wire
format %{"type" => "allow"} / %{"type" => "deny", "reason" => "..."})
or a raw map that is passed through unchanged.
Parameters
result- APermissionResultstruct or raw decision map
Examples
Output.permission_decision(Permission.Result.allow())
Output.permission_decision(Permission.Result.deny("Not allowed"))
# Raw map passthrough
Output.permission_decision(%{"type" => "allow"})
Creates a PermissionRequest hook output that denies the tool.
Shorthand for permission_decision(Permission.Result.deny(reason)).
Parameters
reason- Explanation for denying
Examples
Output.permission_deny("Tool not permitted in this context")
Creates hook output to stop execution.
Parameters
reason- Explanation for stopping
Examples
Output.stop("Critical error detected")
Output.stop("Resource limit exceeded")
Marks output to be suppressed from transcript.
Parameters
output- Existing hook output
Examples
Output.allow()
|> Output.suppress_output()
Converts Elixir output to JSON-compatible map for CLI.
Converts atom keys to strings recursively.
Examples
iex> Output.to_json_map(%{continue: false, stopReason: "Error"})
%{"continue" => false, "stopReason" => "Error"}
iex> Output.to_json_map(%{hookSpecificOutput: %{hookEventName: "PreToolUse"}})
%{"hookSpecificOutput" => %{"hookEventName" => "PreToolUse"}}
Validates hook output structure.
Returns :ok if valid, {:error, reason} otherwise.
Examples
iex> Output.validate(%{continue: true})
:ok
iex> Output.validate("not a map")
{:error, "Hook output must be a map"}
Adds additional context to hook output.
This is a composable alternative to add_context/2 that can be piped
onto existing hook outputs. Works with PostToolUse, UserPromptSubmit,
and SessionStart hooks.
Parameters
output- Existing hook outputcontext- Contextual information to inject
Examples
Output.allow("Approved")
|> Output.with_additional_context("Command took 2.3s")
Output.continue()
|> Output.with_additional_context("Current time: 10:00 AM")
@spec with_async_timeout(t(), non_neg_integer()) :: t()
Sets the timeout for async hook processing.
Must be used with async/1. The timeout is specified in milliseconds
and defines how long the CLI will wait for the async operation to complete.
Parameters
output- Existing hook output (should haveasync: true)timeout_ms- Timeout in milliseconds
Examples
Output.allow("Processing")
|> Output.async()
|> Output.with_async_timeout(60_000) # 60 second timeout
Adds a reason to hook output.
Reasons are shown to Claude to help it understand what happened.
Parameters
output- Existing hook outputreason- Claude-visible explanation
Examples
Output.deny("Invalid path")
|> Output.with_reason("Path must be within /allowed directory")
Adds a system message to hook output.
System messages are shown to the user but not to Claude.
Parameters
output- Existing hook outputmessage- User-visible message
Examples
Output.deny("Command blocked")
|> Output.with_system_message("Security policy violation")
Modifies tool input before execution (PreToolUse hooks only).
This helper allows hooks to sanitize, validate, or transform tool inputs before Claude executes the tool. The updated input replaces the original input for that tool execution.
Parameters
output- Existing hook outputupdated_input- Map of updated input values
Examples
# Sanitize file paths
Output.allow("Path sanitized")
|> Output.with_updated_input(%{"path" => sanitize_path(input["path"])})
# Add default values
Output.allow("Defaults applied")
|> Output.with_updated_input(Map.put(input, "timeout", 30))
# Validate and transform
Output.allow("Input validated")
|> Output.with_updated_input(%{
"path" => expand_path(input["path"]),
"validated" => true
})
Sets updated MCP tool output in hook output.
Used for PostToolUse hooks to modify or annotate MCP tool responses
before they're returned to Claude. Accepts any value type to match
the Python SDK's updatedMCPToolOutput: Any.
Parameters
output- Existing hook outputmcp_output- Updated MCP tool output (any type)
Examples
Output.continue()
|> Output.with_updated_mcp_output(%{"content" => [%{"type" => "text", "text" => "filtered"}]})