Claude Plugin Hooks: Only Four of the Nine Events Work Here

Claude Code plugins support two hook types and nine lifecycle events, but prompt-based hooks run on only four. The video covers command versus prompt hooks, exit codes, config formats and why a hook on PostToolUse silently never runs.

2:44 video3 min readWatch on YouTube

The assumption is that the recommended hook type in a Claude Code plugin fires on every event. The video says it does not: prompt-based hooks work on only four of the nine lifecycle events. Put one on the wrong event and nothing tells you, because it silently never runs.

Two hook types

Prompt-based hooks are the recommended type. They send context to Claude, who decides what to do. A matcher names the tools the hook applies to, a prompt field tells Claude how to respond, and the default timeout is 30 seconds.

Command hooks are deterministic. They run a bash script, capture stdout and stderr, and use the exit code to decide what happens. Exit zero means success and stdout goes into the transcript. Exit two means block: Claude sees the stderr message and stops. Any other code is non-blocking. The default timeout is 60 seconds.

For portability, the command path should use the Claude plugin root environment variable and never a hardcoded absolute path.

Two config formats

The two places to put hooks use formats that are not interchangeable. A plugin's hooks.json wraps everything under a description and a hooks key. The settings file skips that wrapper, and event names sit at the top level.

Nine events, four rules

Nine lifecycle events exist. PreToolUse fires before a tool call, Stop when Claude is about to finish, SubagentStop for subagents, and UserPromptSubmit when you submit a message. Those four support prompt-based hooks. The other five, PostToolUse, SessionStart, SessionEnd, PreCompact and Notification, are command hooks only.

Four rules govern execution:

  • All matching hooks run in parallel, with no ordering, and cannot see each other's output.
  • Hooks load once at session start, so there is no hot swap; restart Claude after editing hooks.
  • Matchers are case-sensitive, so Write is not write.
  • Security defaults apply: quote every bash variable, validate inputs, and deny path traversal and access to files like .env and private keys.

The gap

The catch is that the recommended type fires only on those same four events. Add a prompt-based hook to PostToolUse, expecting it to review a tool's output after the fact, and nothing happens: no error and no warning. If a hook needs to catch something after the tool call, it has to be a command hook.

What the video does not claim

It does not explain why prompt hooks are limited to four events, since the source skill states the restriction and not the reason. It also does not say command hooks are always better; for events where both are allowed, the choice is left to the plugin author.

Key takeaways

  • Claude Code plugins support two hook types and nine lifecycle events.
  • Prompt-based hooks are recommended but fire only on PreToolUse, Stop, SubagentStop and UserPromptSubmit.
  • PostToolUse, SessionStart, SessionEnd, PreCompact and Notification are command-hook only.
  • A prompt-based hook on the wrong event silently never runs.
  • Command hooks use exit codes: zero succeeds, two blocks, anything else does not block.
  • Plugin hooks.json uses a wrapper; the settings file does not.
  • Hooks load at session start, so restart after editing, and matchers are case-sensitive.

Try it yourself

Ask Claude for a plugin that logs every Write tool call to a JSON file and blocks any Write to a .env file. Watch four things. Does the logging hook, which fires after the write on PostToolUse, use a command hook? Does it use the plugin root variable instead of a hardcoded path? Does it use the plugin hooks.json wrapper format? And does it remind you to restart the session after editing hooks.json? Try it on your own plugin idea.

Chapters

  1. 0:00The naive framing: "does it fire on any event?"
  2. 0:10Two types, two formats
  3. 0:58Nine events, four rules
  4. 1:38The gap
  5. 1:59Carry-out
  6. 2:09Your turn
  7. 2:41Outro
Full transcript(auto-generated, with timestamps)

The naive framing: "does it fire on any event?"

[0:00]Someone assumes the recommended hook type fires on every event. It doesn't. It only works on four of the nine. Does a prompt-based hook fire on only four of the nine events I use? Two hook types,

Two types, two formats

[0:10]Two places to put them. Prompt-based is the recommended type. It sends context to Claude who decides what to do. The matcher names the tools that applies to. The prompt field tells Claude how to respond and it times out at 30 seconds by default. Command hooks are deterministic. They run a bash script, capture a stdout and stderr, and use the exit code to decide what happens. Exit zero means success. stdout goes into the transcript. Exit two means block. Claude sees the stderr message and stops. Any other code is non-blocking and the default timeout is 60 seconds. For portability, always use the Claude plugin root environment variable in the command path, never a hardcoded absolute one. The two config formats aren't interchangeable, either. Plugin hooks, JSON wraps everything under a description and a hooks key. The settings file skips that wrapper. And event names sit right at the top level.

Nine events, four rules

[0:58]Nine life cycle events exist. Pre-tool use fires before a tool call. Stop when Claude's about to finish. Sub-agent stop for sub-agents and user prompt submit when you submit a message. Those four support prompt-based hooks. The other five, post-tool use, session start, session end, pre-compact, and notification, are command hook only. Four rules govern execution. All matching hooks run in parallel. There's no ordering. And hooks can't see each other's output. Hooks load once at session start. There's no hot swap, so you restart Claude after editing hooks. JSON matchers are case-sensitive. Write is not write. And the security defaults, quote every bash variable, validate inputs, and deny path traversal and access to files like .env and private keys. Here's the catch. Prompt-based is

The gap

[1:40]The type the skill recommends, but it only fires on those same four events. Pre-tool use, stop, sub-agent stop, and user prompt submit. Add a prompt-based hook to post-tool use expecting it to review a tool's output after the fact and nothing happens. No error, no warning. It just never runs. If a hook needs to catch something after the tool call, that has to be a command hook.

Carry-out

[1:59]Prompt-based hooks are the recommended type, but they only fire on four of the nine life cycle events. Put one on post tool use, and it silently never runs. Your turn. Give Claude a plugin that

Your turn

[2:10]Logs every write tool call to a JSON file and blocks any write to a dot n file. Watch four things. For the logging hook, it fires after the write happens on post tool use. Does Claude reach for command hook since prompt-based won't run there at all? Does it use the Claude plugin root environment variable in the command path instead of a hard-coded one? Does it put the hook in the plugins hooks.json wrapper format? Or does it slip into the flat settings format by mistake? And after it edits hooks.json, does it tell you to restart the session before you test it? Only four of the nine lay 'em in for bear.

More from Extending Claude — Skills, Plugins & Connectors

Humanitarians AI Lyrical Literacy Project