Claude Code hooks development guide. TRIGGERS - create hook, PostToolUse, PreToolUse, Stop hook, hook lifecycle, decision block.
From itp-hooksnpx claudepluginhub terrylica/cc-skills --plugin itp-hooksThis skill is limited to using the following tools:
references/debugging-guide.mdreferences/evolution-log.mdreferences/hook-templates.mdreferences/lifecycle-reference.mdreferences/visibility-patterns.mdDispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Guides idea refinement into designs: explores context, asks questions one-by-one, proposes approaches, presents sections for approval, writes/review specs before coding.
Guide for developing Claude Code hooks with proper output visibility patterns.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
decision: block patternCritical 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 |
/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:
jq -n to generate valid JSON"decision": "block" for visibility1. [pending] Create hook script with shebang and set -euo pipefail
2. [pending] Parse PAYLOAD from stdin with jq
3. [pending] Add condition check for when to trigger
4. [pending] Output JSON with decision:block pattern
5. [pending] Register hook in hooks.json with matcher
6. [pending] Test by editing a matching file
7. [pending] Verify Claude sees the message in system-reminder
1. [pending] Verify hook executes (add debug log to /tmp)
2. [pending] Check JSON format is valid (pipe to jq .)
3. [pending] Confirm decision:block is present in output
4. [pending] Verify exit code is 0
5. [pending] Check hooks.json matcher pattern
6. [pending] Restart Claude Code session
When this skill is updated:
| Issue | Cause | Solution |
|---|---|---|
| Hook output not visible | Missing decision:block in JSON | Add "decision": "block" to JSON output |
| JSON parse error in hook | Invalid JSON syntax | Use jq -n to generate valid JSON |
| Hook not executing | Wrong matcher pattern | Check hooks.json matcher regex matches tool name |
| Plain text output ignored | Only JSON parsed | Wrap output in JSON with decision:block |
| Exit code 2 behavior | stderr used instead of stdout | Use exit 0 with JSON, or exit 2 for stderr messages |
| Session not seeing changes | Hooks cached | Restart Claude Code session after hook changes |
| Verbose mode not showing | Disabled by default | Enable verbose mode in Claude Code settings |
| jq command not found | jq not installed | brew install jq |
After this skill completes, check before closing:
Only update if the issue is real and reproducible — not speculative.