Cc-skills hooks-development

Claude Code hooks development guide. TRIGGERS - create hook, PostToolUse, PreToolUse, Stop hook, hook lifecycle, decision block.

install
source · Clone the upstream repo
git clone https://github.com/terrylica/cc-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/terrylica/cc-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/itp-hooks/skills/hooks-development" ~/.claude/skills/terrylica-cc-skills-hooks-development && rm -rf "$T"
manifest: plugins/itp-hooks/skills/hooks-development/SKILL.md
source content

Hooks Development

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.

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 FormatClaude Visibility
Plain textNot visible
JSON without
decision: block
Not visible
JSON with
decision: block
Visible

Exit code behavior:

Exit Codestdout BehaviorClaude Visibility
0JSON parsed, shown in verbose mode onlyOnly if
"decision": "block"
2Ignored, uses stderr insteadstderr shown to Claude
Otherstderr shown in verbose modeNot shown to Claude

Minimal Working Pattern

/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

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

Debugging Invisible Hook Output

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

Reference Documentation


Post-Change Checklist (Self-Evolution)

When this skill is updated:


Related Resources


Troubleshooting

IssueCauseSolution
Hook output not visibleMissing decision:block in JSONAdd
"decision": "block"
to JSON output
JSON parse error in hookInvalid JSON syntaxUse
jq -n
to generate valid JSON
Hook not executingWrong matcher patternCheck hooks.json matcher regex matches tool name
Plain text output ignoredOnly JSON parsedWrap output in JSON with decision:block
Exit code 2 behaviorstderr used instead of stdoutUse exit 0 with JSON, or exit 2 for stderr messages
Session not seeing changesHooks cachedRestart Claude Code session after hook changes
Verbose mode not showingDisabled by defaultEnable verbose mode in Claude Code settings
jq command not foundjq not installed
brew install jq

Post-Execution Reflection

After this skill completes, check before closing:

  1. Did the command succeed? — If not, fix the instruction or error table that caused the failure.
  2. Did parameters or output change? — If the underlying tool's interface drifted, update Usage examples and Parameters table to match.
  3. Was a workaround needed? — If you had to improvise (different flags, extra steps), update this SKILL.md so the next invocation doesn't need the same workaround.

Only update if the issue is real and reproducible — not speculative.