Events

The twelve lifecycle events Claude Code fires. For the lifecycle narrative, see how hooks work.

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

from captain_hook import hook, Event

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

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 When a session starts, resumes, clears, or compacts source No — advisory
PreToolUse PreToolUseEvent Before a tool runs tool_name, cmd, file, content, old, agent_type Yes — deny the tool
PermissionRequest PermissionRequestEvent When a permission dialog would be shown permission_suggestions, agent_type, plus the tool accessors tool_name, cmd, file, … Yes — answer the dialog with allow, deny, or rewrite
PostToolUse PostToolUseEvent After a tool succeeds tool_name, command, file, content, tool_response No — advisory
PostToolUseFailure PostToolUseFailureEvent After a tool fails tool_name, error, is_interrupt No — advisory
UserPromptSubmit UserPromptSubmitEvent When the user submits a prompt user_prompt No — advisory
Stop StopEvent When the agent is about to stop stop_hook_active, background_tasks, session_crons Yes — force-continue
SubagentStop SubagentStopEvent When a subagent finishes agent_type, background_tasks, session_crons Yes — force-continue
SubagentStart SubagentStartEvent When a subagent launches agent_type No — advisory
PreCompact PreCompactEvent Before context compaction trigger, custom_instructions No — advisory
Notification NotificationEvent On a system notification message, title, notification_type No — advisory
SessionEnd 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 approve it silently, 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 and 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() reads.

Shared properties

Every event also exposes the base accessors: event, session_id, is_subagent, transcript_path, cwd, permission_mode, skip_permissions, parent_agent_type, stop_hook_active, tasks, and ctx. cwd is the session’s working directory when the event fired, for resolving relative tool paths. 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 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 and BashCall. evt.as_input(call_type) narrows it:

Method Returns
evt.input The parsed ToolCall; OtherCall for an unrecognized tool, FallbackCall for input outside the JSON contract
evt.as_input(ReadCall) evt.input typed as ReadCall, or None when the event is a different tool

For a non-matching tool as_input returns None instead of raising, so a hook reads typed fields without an isinstance check:

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 carries .file_path, .offset, and .limit; each *Call type exposes the fields of its tool. For a declarative form, subclass CustomInputTypeCondition[ReadCall].