Events
The thirteen 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 |
Yes — cancel the compaction; a warn’s message joins its custom instructions |
Notification |
NotificationEvent | On a system notification | message, title, notification_type |
No — advisory |
SessionEnd |
SessionEndEvent | When the session ends | reason |
No — output ignored |
MessageDisplay |
MessageDisplayEvent | For each streamed chunk of an assistant reply | message_id, index, final, delta |
No — a rewrite replaces the chunk’s displayed text |
Host events
One more event exists, and Claude Code never fires it. The capt-hookd host raises ResourcePressure, typed as ResourcePressureEvent, when a child of a session stays resource-heavy. It has no entry in hooks.json, so it adds nothing to the thirteen lifecycle events above. The performance pack’s resource monitor is its only handler. See Tune the subprocess resource monitor for the thresholds.
The event arrives in one of three stages, readable as evt.stage:
| Stage | Fires |
|---|---|
| warn | On the first sustained sample of heavy CPU or disk use |
judge |
When the grace period ends and the child is still heavy |
escalate |
When the child outlives a SIGTERM |
| Property | Meaning |
|---|---|
stage |
"warn", "judge", or "escalate" |
process |
The child’s identity and usage as a mapping |
pid, ppid, pgid |
The child’s process ids |
start_unix, start_usec |
The child’s start time |
comm, argv |
The child’s command name and arguments |
process_cwd |
The child’s working directory, or None |
runtime_s |
How long the child has run, in seconds |
cpu_fraction, disk_bps |
Measured usage, or None when the reading was unavailable |
claude_pid, claude_start_unix |
The session’s claude process, which anchors ownership |
The host refuses a ResourcePressure event that a client submits, so only the host can raise it.
Only the warn stage reads the answer. A warn or an allow renders {"decision": "proceed"}, which tells the host the stage ran. For a warn, the host reads anything else as a refusal and stops tracking that child, so the child is left running and is not warned about again. The host does not read the answers to judge and escalate. The worker records a notice for each refusal instead, and the next hook event delivers it to the session.
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. A MessageDisplay rewrite replaces what the user sees of one reply chunk, and an empty message hides it. A PreCompact block cancels the compaction, and any other message is appended to the summarizer’s custom instructions. 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].