# Events

The twelve lifecycle events Claude Code fires. For the lifecycle narrative, see [how hooks work](../../docs/guide/how-it-works.md).

A hook targets one event, or several combined with `|`:

``` python
from captain_hook import hook, Event

hook(Event.Stop | Event.SubagentStop, message="Review before finishing", block=True)
```

[Event](../../reference/events-results.md#captain_hook.Event) is a `Flag` enum, so `|` registers one hook for every named event at once.


# Event reference

Each row lists the event's typed class, when it fires, its key accessors, and whether it can block.

| Event | Event class | Fires | Key properties | Can block? |
|----|----|----|----|----|
| `SessionStart` | [SessionStartEvent](../../reference/events-results.md#captain_hook.SessionStartEvent) | When a session starts, resumes, clears, or compacts | [source](../../reference/files-commands.md#captain_hook.Call.source) | No -- advisory |
| `PreToolUse` | [PreToolUseEvent](../../reference/events-results.md#captain_hook.PreToolUseEvent) | Before a tool runs | `tool_name`, [cmd](../../reference/events-results.md#captain_hook.BaseHookEvent.cmd), `file`, `content`, `old`, `agent_type` | **Yes** -- deny the tool |
| `PermissionRequest` | [PermissionRequestEvent](../../reference/events-results.md#captain_hook.PermissionRequestEvent) | When a permission dialog would be shown | `permission_suggestions`, `agent_type`, plus the tool accessors `tool_name`, [cmd](../../reference/events-results.md#captain_hook.BaseHookEvent.cmd), `file`, … | **Yes** -- answer the dialog with allow, deny, or rewrite |
| `PostToolUse` | [PostToolUseEvent](../../reference/events-results.md#captain_hook.PostToolUseEvent) | After a tool succeeds | `tool_name`, [command](../../reference/files-commands.md#captain_hook.Call.command), `file`, `content`, `tool_response` | No -- advisory |
| `PostToolUseFailure` | [PostToolUseFailureEvent](../../reference/events-results.md#captain_hook.PostToolUseFailureEvent) | After a tool fails | `tool_name`, [error](../../reference/tool-calls.md#captain_hook.OtherCall.error), `is_interrupt` | No -- advisory |
| `UserPromptSubmit` | [UserPromptSubmitEvent](../../reference/events-results.md#captain_hook.UserPromptSubmitEvent) | When the user submits a prompt | `user_prompt` | No -- advisory |
| `Stop` | [StopEvent](../../reference/events-results.md#captain_hook.StopEvent) | When the agent is about to stop | `stop_hook_active`, [background_tasks](../../reference/events-results.md#captain_hook.BaseHookEvent.background_tasks), [session_crons](../../reference/events-results.md#captain_hook.BaseHookEvent.session_crons) | **Yes** -- force-continue |
| `SubagentStop` | [SubagentStopEvent](../../reference/events-results.md#captain_hook.SubagentStopEvent) | When a subagent finishes | `agent_type`, [background_tasks](../../reference/events-results.md#captain_hook.BaseHookEvent.background_tasks), [session_crons](../../reference/events-results.md#captain_hook.BaseHookEvent.session_crons) | **Yes** -- force-continue |
| `SubagentStart` | [SubagentStartEvent](../../reference/events-results.md#captain_hook.SubagentStartEvent) | When a subagent launches | `agent_type` | No -- advisory |
| `PreCompact` | [PreCompactEvent](../../reference/events-results.md#captain_hook.PreCompactEvent) | Before context compaction | `trigger`, `custom_instructions` | No -- advisory |
| `Notification` | [NotificationEvent](../../reference/events-results.md#captain_hook.NotificationEvent) | On a system notification | `message`, `title`, `notification_type` | No -- advisory |
| `SessionEnd` | [SessionEndEvent](../../reference/events-results.md#captain_hook.SessionEndEvent) | When the session ends | `reason` | No -- output ignored |

`PreToolUse` and `PermissionRequest` prevent an action. A `PreToolUse` block denies the tool call outright, and a `PermissionRequest` result answers the pending dialog. `allow` and [rewrite](../../reference/events-results.md#captain_hook.ToolRewriteEvent.rewrite) approve it silently, [block](../../reference/prompt-contexts.md#captain_hook.Excerpts.block) denies it with the message shown to the user, and `None` or a warn lets the dialog show normally. `Stop` and `SubagentStop` force the agent to keep going. Every other event is advisory -- a hook injects context but cannot block.

On `Stop` and `SubagentStop`, [background_tasks](../../reference/events-results.md#captain_hook.BaseHookEvent.background_tasks) and [session_crons](../../reference/events-results.md#captain_hook.BaseHookEvent.session_crons) report the background work and scheduled prompts holding the session open -- a non-empty tuple means it is paused for that work, not finished, which is what [Waiting()](../../reference/conditions.md#captain_hook.Waiting) reads.


# Shared properties

Every event also exposes the base accessors: `event`, `session_id`, [is_subagent](../../reference/events-results.md#captain_hook.BaseHookEvent.is_subagent), `transcript_path`, `cwd`, `permission_mode`, [skip_permissions](../../reference/events-results.md#captain_hook.BaseHookEvent.skip_permissions), `parent_agent_type`, `stop_hook_active`, [tasks](../../reference/events-results.md#captain_hook.BaseHookEvent.tasks), and `ctx`. `cwd` is the session's working directory when the event fired, for resolving relative tool paths. [skip_permissions](../../reference/events-results.md#captain_hook.BaseHookEvent.skip_permissions) walks the process tree and reports whether the session's `claude` process was launched with `--dangerously-skip-permissions` or `--allow-dangerously-skip-permissions`. [tasks](../../reference/events-results.md#captain_hook.BaseHookEvent.tasks) is the live Claude Code task list; `ctx` is the hook context, with the transcript, settings, and the current turn.


# Typed tool input

`evt.input` is the parsed tool call for the current event, typed as one of the `*Call` classes such as [ReadCall](../../reference/tool-calls.md#captain_hook.ReadCall) and [BashCall](../../reference/tool-calls.md#captain_hook.BashCall). `evt.as_input(call_type)` narrows it:

| Method | Returns |
|----|----|
| `evt.input` | The parsed [ToolCall](../../reference/tool-calls.md#captain_hook.ToolCall); [OtherCall](../../reference/tool-calls.md#captain_hook.OtherCall) for an unrecognized tool, `FallbackCall` for input outside the JSON contract |
| `evt.as_input(ReadCall)` | `evt.input` typed as [ReadCall](../../reference/tool-calls.md#captain_hook.ReadCall), or `None` when the event is a different tool |

For a non-matching tool [as_input](../../reference/events-results.md#captain_hook.BaseHookEvent.as_input) returns `None` instead of raising, so a hook reads typed fields without an `isinstance` check:

``` python
from captain_hook import ReadCall

call = evt.as_input(ReadCall)
if call and call.limit and call.limit > 1000:
    return evt.warn("Large read; narrow with offset/limit.")
```

[ReadCall](../../reference/tool-calls.md#captain_hook.ReadCall) carries `.file_path`, `.offset`, and `.limit`; each `*Call` type exposes the fields of its tool. For a declarative form, subclass [`CustomInputTypeCondition[ReadCall]`](../../docs/reference/conditions.md#custom-conditions).
