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.
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].