Claude Code hooks development guide covering all 10 hook events lifecycle, PostToolUse visibility patterns, PreToolUse guards, Stop hook schema, and debugging. Use when creating hooks, troubleshooting hook output, understanding hook lifecycle, or when user mentions decision block, hook JSON output, stop hook, or Claude Code hooks.
View on GitHubterrylica/cc-skills
itp-hooks
January 25, 2026
Select agents to install to:
npx add-skill https://github.com/terrylica/cc-skills/blob/main/plugins/itp-hooks/skills/hooks-development/SKILL.md -a claude-code --skill hooks-developmentInstallation paths:
.claude/skills/hooks-development/# Hooks Development
Guide for developing Claude Code hooks with proper output visibility patterns.
## When to Use This Skill
- Creating a new PostToolUse or PreToolUse hook
- Hook output is not visible to Claude (most common issue)
- User asks about `decision: block` pattern
- Debugging why hook messages don't appear
- User mentions "Claude Code hooks" or "hook visibility"
---
## Quick Reference: Visibility Patterns
**Critical insight**: PostToolUse hook stdout is only visible to Claude when JSON contains `"decision": "block"`.
| Output Format | Claude Visibility |
| ------------------------------ | ----------------- |
| Plain text | Not visible |
| JSON without `decision: block` | Not visible |
| JSON with `decision: block` | Visible |
**Exit code behavior**:
| Exit Code | stdout Behavior | Claude Visibility |
| --------- | --------------------------------------- | ----------------------------- |
| **0** | JSON parsed, shown in verbose mode only | Only if `"decision": "block"` |
| **2** | Ignored, uses stderr instead | stderr shown to Claude |
| **Other** | stderr shown in verbose mode | Not shown to Claude |
---
## Minimal Working Pattern
```bash
/usr/bin/env bash << 'SKILL_SCRIPT_EOF'
#!/usr/bin/env bash
set -euo pipefail
# Read hook payload from stdin
PAYLOAD=$(cat)
FILE_PATH=$(echo "$PAYLOAD" | jq -r '.tool_input.file_path // empty')
[[ -z "$FILE_PATH" ]] && exit 0
# Your condition here
if [[ condition_met ]]; then
jq -n \
--arg reason "[HOOK] Your message to Claude" \
'{decision: "block", reason: $reason}'
fi
exit 0
SKILL_SCRIPT_EOF
```
**Key points**:
1. Use `jq -n` to generate valid JSON
2. Include `"decision": "block"` for visibility
3. Exit with code 0
4. The "blocking error" label is cosmetic - operation continues
---
## TodoWrite Templates
### Creating a PostToolUse Hook