Hook lifecycle
Claude Code runs hooks at specific points during a session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, input arrives on stdin. For HTTP hooks, it arrives as the POST request body. Your handler can then inspect the input, take action, and optionally return a decision. Events fall into three cadences:- once per session:
SessionStartandSessionEnd - once per turn:
UserPromptSubmit,Stop, andStopFailure - on every tool call inside the agentic loop:
PreToolUseandPostToolUse, exceptEndConversationcalls, which skip both
How a hook resolves
To see how these pieces fit together, consider thisPreToolUse hook that blocks destructive shell commands.
- macOS/Linux
- Windows (PowerShell)
The The script reads the JSON input from stdin, extracts the command, and returns a
matcher narrows to Bash tool calls and the if condition narrows further to Bash subcommands matching rm *, so block-rm.sh only spawns when both filters match:permissionDecision of "deny" if it contains rm -rf. Save it to .claude/hooks/block-rm.sh in your project and make it executable with chmod +x .claude/hooks/block-rm.sh so Claude Code can run it: