← All guidesHooks

What every Claude Code hook event means

Claude Code fires a hook at each meaningful point in a session. If you already know how hooks are wired up (see the complete guide to Claude Code hooks), this page is the reference: what each event means, when it fires, what it carries, and what it is good for.

What every event carries

Whatever the event, your command receives a JSON object on standard input with the same four base fields: session_id, transcript_path (the session's log on disk), cwd (the directory the session runs in), and hook_event_name. Everything below is what each event adds on top.

The session lifecycle, event by event

SessionStart

Fires when a session opens. The payload adds source, telling you how it opened: a fresh start, a resume, a /clear, or a restart after a compaction. Good for setting up state, logging that a session began, or announcing a new agent. Anything your command prints on stdout is added to Claude's context, which makes this the place to inject the current branch, an open ticket number, or a deploy status.

{
  "hooks": {
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": "echo \"Current branch: $(git branch --show-current)\"" }] }
    ]
  }
}

UserPromptSubmit

Fires when you submit a prompt and a new turn begins, before Claude sees it. The payload adds prompt, the text you typed. This is your "the agent just started working" signal: use it to mark a session as busy or to timestamp the start of a turn. Like SessionStart, its stdout is appended to the context, and exiting with code 2 cancels the prompt entirely, which is a blunt but effective way to enforce a house rule.

PreToolUse

Fires just before Claude runs a tool (Bash, Edit, Read, Write, and so on). The payload adds tool_name and tool_input, the exact arguments about to be used. Because it runs before the tool, it is the only event that can inspect and gate a tool call: exit 2 to block it, or print a JSON decision to allow, deny or force a prompt. Its matcher selects which tools trigger it. See gate tool calls with the PreToolUse hook for the details.

PostToolUse

Fires right after a tool call returns, with tool_name, tool_input and tool_response. Use it to react to what a tool did: run a formatter after an edit, a linter after a write, or simply note that the agent is still working. Blocking here does not undo the tool call, but exit code 2 feeds your message back to Claude, so a failing linter can push it to fix its own edit:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{ "type": "command", "command": "npx prettier --write \"$CLAUDE_PROJECT_DIR\"" }]
      }
    ]
  }
}

Notification

Fires when Claude needs your attention: most commonly to ask you to approve a step it cannot take on its own, and also when a prompt has been sitting idle for a while. The payload adds message, the text Claude would have shown you. This is the event to wire to a desktop notification: it means a session is blocked on you, and every minute it waits is wasted.

Stop

Fires when Claude finishes its turn and hands control back. This is the "run is done" signal, the one most people want first. The payload adds stop_hook_active, which is true when the turn is already continuing because of a previous Stop hook. Check it before blocking, or you will build an infinite loop. Full write-up in the Claude Code Stop hook, explained.

SubagentStop

Fires when a subagent, spawned through the Task tool, finishes, distinct from Stop, which only fires when the whole turn ends. On a run that fans work out to several subagents, this lets you track them completing independently. More in understanding Claude Code subagents.

PreCompact

Fires when Claude compacts the context on a long run to keep going. The payload adds trigger, either manual (you typed /compact) or auto (the window filled up). It is a good marker that a session is mid-maintenance rather than idle or stuck, and a decent cue that the session has been running long enough to be worth splitting.

SessionEnd

Fires when the session closes, with reason explaining why. The counterpart to SessionStart: clean up state, close a log, or remove whatever you showed for that session. It cannot block the shutdown, so keep it to teardown.

Which matcher applies where

Only the tool events take a matcher, tested as a regular expression against the tool name:

EventMatcherTypical values
PreToolUse, PostToolUseYesBash, Edit|Write, mcp__.*, .*
Notification, Stop, SubagentStopNoomit the key
SessionStart, SessionEnd, PreCompactNoomit the key
UserPromptSubmitNoomit the key

Leaving the matcher out on a lifecycle event runs the hooks every time, which is what you want.

Which events can block

Not every event can influence the run. PreToolUse can cancel a tool call before it happens. UserPromptSubmit can cancel a prompt. PostToolUse and Stop cannot undo what happened, but they can send a message back to Claude and ask it to keep going. Notification, PreCompact, SessionStart and SessionEnd are observational: whatever they return, the session carries on.

Reading a session as states

Line these events up and a session tells a story: it starts (SessionStart), you prompt it (UserPromptSubmit), it works through tools (PreToolUse / PostToolUse), it may pause for you (Notification), it compacts on long runs (PreCompact), and it finishes (Stop) before eventually closing (SessionEnd).

That mapping from events to states is exactly what Blooby does automatically. It registers a hook on every event above and turns the stream into a live desktop mascot per session: idle, working, needs-you, compacting, done. You do not write any of the hooks yourself; you just watch the corner of your screen.

See it for yourself

download Get Blooby free