# `ClaudeAgentSDK.Hooks.Output`
[🔗](https://github.com/nshkrdotcom/claude_agent_sdk/blob/v0.15.0/lib/claude_agent_sdk/hooks/output.ex#L1)

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

# `composable_hook_specific_output`

```elixir
@type composable_hook_specific_output() :: %{
  optional(:hookEventName) =&gt; String.t(),
  optional(:permissionDecision) =&gt; String.t() | map(),
  optional(:permissionDecisionReason) =&gt; String.t(),
  optional(:additionalContext) =&gt; String.t(),
  optional(:updatedInput) =&gt; map(),
  optional(:updatedMCPToolOutput) =&gt; term(),
  optional(:decision) =&gt; map(),
  optional(atom()) =&gt; 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.

# `hook_specific_output`

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

# `permission_decision`

```elixir
@type permission_decision() :: :allow | :deny | :ask
```

Permission decision for PreToolUse hooks.

# `permission_request_output`

```elixir
@type permission_request_output() :: %{hookEventName: String.t(), decision: map()}
```

PermissionRequest hook-specific output.

Controls permission dialogs programmatically:
- `hookEventName` - Must be "PermissionRequest"
- `decision` - Permission decision map (e.g. `%{"type" => "allow"}`)

# `post_tool_use_output`

```elixir
@type post_tool_use_output() :: %{
  hookEventName: String.t(),
  additionalContext: String.t()
}
```

PostToolUse hook-specific output.

Adds context for Claude to consider:
- `hookEventName` - Must be "PostToolUse"
- `additionalContext` - Information about tool execution

# `pre_tool_use_output`

```elixir
@type pre_tool_use_output() :: %{
  :hookEventName =&gt; String.t(),
  :permissionDecision =&gt; String.t(),
  :permissionDecisionReason =&gt; String.t(),
  optional(:updatedInput) =&gt; map()
}
```

PreToolUse hook-specific output.

Controls whether a tool call proceeds:
- `hookEventName` - Must be "PreToolUse"
- `permissionDecision` - "allow", "deny", or "ask"
- `permissionDecisionReason` - Explanation for the decision
- `updatedInput` - Optional modified tool input (via `with_updated_input/2`)

# `session_start_output`

```elixir
@type session_start_output() :: %{
  hookEventName: String.t(),
  additionalContext: String.t()
}
```

SessionStart hook-specific output.

Adds context when session starts:
- `hookEventName` - Must be "SessionStart"
- `additionalContext` - Initial context for session

# `t`

```elixir
@type t() :: %{
  optional(:continue) =&gt; boolean(),
  optional(:stopReason) =&gt; String.t(),
  optional(:suppressOutput) =&gt; boolean(),
  optional(:systemMessage) =&gt; String.t(),
  optional(:reason) =&gt; String.t(),
  optional(:decision) =&gt; String.t(),
  optional(:hookSpecificOutput) =&gt; hook_specific_output(),
  optional(atom()) =&gt; term()
}
```

Complete hook output map.

All fields are optional. The CLI processes these fields to control behavior.

# `user_prompt_submit_output`

```elixir
@type user_prompt_submit_output() :: %{
  hookEventName: String.t(),
  additionalContext: String.t()
}
```

UserPromptSubmit hook-specific output.

Adds context before processing prompt:
- `hookEventName` - Must be "UserPromptSubmit"
- `additionalContext` - Contextual information to inject

# `add_context`

```elixir
@spec add_context(String.t(), String.t()) :: t()
```

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")

# `allow`

```elixir
@spec allow(String.t()) :: t()
```

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")

# `ask`

```elixir
@spec ask(String.t()) :: t()
```

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")

# `async`

```elixir
@spec async(t()) :: t()
```

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)

# `block`

```elixir
@spec block(String.t()) :: t()
```

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")

# `continue`

```elixir
@spec continue() :: t()
```

Creates hook output to continue execution.

## Examples

    Output.continue()
    # => %{continue: true}

# `deny`

```elixir
@spec deny(String.t()) :: t()
```

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")

# `permission_allow`

```elixir
@spec permission_allow() :: t()
```

Creates a PermissionRequest hook output that allows the tool.

Shorthand for `permission_decision(Permission.Result.allow())`.

## Examples

    Output.permission_allow()

# `permission_decision`

```elixir
@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` - A `PermissionResult` struct 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"})

# `permission_deny`

```elixir
@spec permission_deny(String.t()) :: t()
```

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")

# `stop`

```elixir
@spec stop(String.t()) :: t()
```

Creates hook output to stop execution.

## Parameters

- `reason` - Explanation for stopping

## Examples

    Output.stop("Critical error detected")
    Output.stop("Resource limit exceeded")

# `suppress_output`

```elixir
@spec suppress_output(t()) :: t()
```

Marks output to be suppressed from transcript.

## Parameters

- `output` - Existing hook output

## Examples

    Output.allow()
    |> Output.suppress_output()

# `to_json_map`

```elixir
@spec to_json_map(t()) :: map()
```

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"}}

# `validate`

```elixir
@spec validate(t()) :: :ok | {:error, String.t()}
```

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"}

# `with_additional_context`

```elixir
@spec with_additional_context(t(), String.t()) :: t()
```

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 output
- `context` - 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")

# `with_async_timeout`

```elixir
@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 have `async: true`)
- `timeout_ms` - Timeout in milliseconds

## Examples

    Output.allow("Processing")
    |> Output.async()
    |> Output.with_async_timeout(60_000)  # 60 second timeout

# `with_reason`

```elixir
@spec with_reason(t(), String.t()) :: t()
```

Adds a reason to hook output.

Reasons are shown to Claude to help it understand what happened.

## Parameters

- `output` - Existing hook output
- `reason` - Claude-visible explanation

## Examples

    Output.deny("Invalid path")
    |> Output.with_reason("Path must be within /allowed directory")

# `with_system_message`

```elixir
@spec with_system_message(t(), String.t()) :: t()
```

Adds a system message to hook output.

System messages are shown to the user but not to Claude.

## Parameters

- `output` - Existing hook output
- `message` - User-visible message

## Examples

    Output.deny("Command blocked")
    |> Output.with_system_message("Security policy violation")

# `with_updated_input`

```elixir
@spec with_updated_input(t(), map()) :: t()
```

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 output
- `updated_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
    })

# `with_updated_mcp_output`

```elixir
@spec with_updated_mcp_output(t(), term()) :: t()
```

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 output
- `mcp_output` - Updated MCP tool output (any type)

## Examples

    Output.continue()
    |> Output.with_updated_mcp_output(%{"content" => [%{"type" => "text", "text" => "filtered"}]})

---

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