Current section

Files

Jump to
claude_code lib claude_code message system_message hook_response.ex
Raw

lib/claude_code/message/system_message/hook_response.ex

defmodule ClaudeCode.Message.SystemMessage.HookResponse do
@moduledoc """
Represents a hook response system message from the Claude CLI.
Emitted when a hook completes execution with its final result.
## Fields
- `:type` - Always `:system`
- `:subtype` - Always `:hook_response`
- `:session_id` - Session identifier
- `:uuid` - Message UUID
- `:hook_id` - Unique identifier for this hook execution
- `:hook_name` - Name of the hook that executed
- `:hook_event` - Event that triggered the hook
- `:output` - Combined or processed output
- `:stdout` - Standard output from the hook process
- `:stderr` - Standard error from the hook process
- `:exit_code` - Exit code of the hook process
- `:outcome` - Outcome of the hook execution (`:success`, `:error`, or `:cancelled`)
## JSON Format
```json
{
"type": "system",
"subtype": "hook_response",
"session_id": "...",
"uuid": "...",
"hook_id": "hook_abc123",
"hook_name": "my_hook",
"hook_event": "on_tool_start",
"output": "done",
"stdout": "done",
"stderr": null,
"exit_code": 0,
"outcome": "success"
}
```
"""
use ClaudeCode.JSONEncoder
@enforce_keys [:type, :subtype, :session_id, :hook_id, :hook_name, :hook_event]
defstruct [
:type,
:subtype,
:uuid,
:session_id,
:hook_id,
:hook_name,
:hook_event,
:output,
:stdout,
:stderr,
:exit_code,
:outcome
]
@type t :: %__MODULE__{
type: :system,
subtype: :hook_response,
uuid: String.t() | nil,
session_id: String.t(),
hook_id: String.t(),
hook_name: String.t(),
hook_event: String.t(),
output: String.t() | nil,
stdout: String.t() | nil,
stderr: String.t() | nil,
exit_code: integer() | nil,
outcome: :success | :error | :cancelled | nil
}
@doc """
Creates a new HookResponse from JSON data.
The `"outcome"` string is parsed to an atom (`:success`, `:error`, or `:cancelled`).
## Examples
iex> HookResponse.new(%{
...> "type" => "system",
...> "subtype" => "hook_response",
...> "session_id" => "session-1",
...> "hook_id" => "hook_abc",
...> "hook_name" => "my_hook",
...> "hook_event" => "on_tool_start",
...> "exit_code" => 0,
...> "outcome" => "success"
...> })
{:ok, %HookResponse{type: :system, subtype: :hook_response, outcome: :success, ...}}
iex> HookResponse.new(%{"type" => "assistant"})
{:error, :invalid_message_type}
"""
@spec new(map()) :: {:ok, t()} | {:error, atom()}
def new(
%{
"type" => "system",
"subtype" => "hook_response",
"session_id" => session_id,
"hook_id" => hook_id,
"hook_name" => hook_name,
"hook_event" => hook_event
} = json
) do
{:ok,
%__MODULE__{
type: :system,
subtype: :hook_response,
uuid: json["uuid"],
session_id: session_id,
hook_id: hook_id,
hook_name: hook_name,
hook_event: hook_event,
output: json["output"],
stdout: json["stdout"],
stderr: json["stderr"],
exit_code: json["exit_code"],
outcome: parse_outcome(json["outcome"])
}}
end
def new(%{"type" => "system", "subtype" => "hook_response"}), do: {:error, :missing_required_fields}
def new(_), do: {:error, :invalid_message_type}
@doc """
Type guard to check if a value is a HookResponse.
"""
@spec hook_response?(any()) :: boolean()
def hook_response?(%__MODULE__{type: :system, subtype: :hook_response}), do: true
def hook_response?(_), do: false
defp parse_outcome("success"), do: :success
defp parse_outcome("error"), do: :error
defp parse_outcome("cancelled"), do: :cancelled
defp parse_outcome(_), do: nil
end