From itp-hooks
Claude Code hooks development guide. TRIGGERS - create hook, PostToolUse, PreToolUse, Stop hook, hook lifecycle, decision block.
How this skill is triggered — by the user, by Claude, or both
Slash command
/itp-hooks:hooks-developmentThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Guide for developing Claude Code hooks with proper output visibility patterns.
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 visibilityDefault to shell (bash) for hooks. A hook fires on every matching event and
~95% bail out immediately, so process-startup latency dominates — not the
logic. Measured on an m3max: a full bash hook (keyword match + bail) ≈ 7 ms;
a do-nothing Bun script ≈ 17 ms. bash also needs no runtime dependency and
matches existing hook convention. Keep the hot-path bail-out a bash builtin
case (no jq/process spawn) — see the pre-jq-fastpath in the devops-tools SCS
hooks.
Reach for Bun/TypeScript only when a hook does real work that amortizes the ~10 ms startup tax: parsing/transforming structured data, calling an API, sharing typed modules across many hooks, or maintaining state. For simple match-and-emit reminders, shell wins on speed, durability, and convention. (This is the language doctrine's "existing convention + SOTA-native ecosystem override the Bun-first default" clause in action.)
1. [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.
npx claudepluginhub terrylica/cc-skills --plugin itp-hooksGuides creating Node.js .cjs hook scripts for Claude Code plugins: hooks.json config, events like PreToolUse, stdio processing, timeouts, paths, and testing.
Claude Code hooks configuration and development. Use when the user mentions hooks, PreToolUse, PostToolUse, SessionStart, SubagentStart, PermissionRequest, or TaskCompleted.
Authors Python hooks for Claude Code with JSON output schemas, TypedDict templates for PreToolUse, PostToolUse, UserPromptSubmit events. Validates formats and applies fail-open principles.