creating-hooks

STARTER_CHARACTER = 🪝

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "creating-hooks" with this command: npx skills add lexler/skill-factory/lexler-skill-factory-creating-hooks

STARTER_CHARACTER = 🪝

Setup

First, update the reference docs to get the latest from Anthropic:

python ~/.claude/skills/creating-hooks/scripts/update-docs.py

What Hooks Are

Shell commands that execute at lifecycle points in Claude Code. Unlike prompts, hooks are deterministic—they always run when triggered.

Configuration

Hooks live in settings files:

  • ~/.claude/settings.json

  • User settings (all projects)

  • .claude/settings.json

  • Project settings (shared via git)

  • .claude/settings.local.json

  • Local project settings (not committed)

{ "hooks": { "EventName": [ { "matcher": "ToolPattern", "hooks": [ { "type": "command", "command": "your-command-here" } ] } ] } }

Matcher: Pattern to match tool names (case-sensitive)

  • Exact match: Write

  • Regex: Edit|Write

  • All tools: * or omit

Environment variables:

  • $CLAUDE_PROJECT_DIR

  • Absolute path to project root

  • $CLAUDE_ENV_FILE

  • File path for persisting env vars (SessionStart only)

Hook Events

Tool events (matcher applies):

  • PreToolUse

  • Before tool executes

  • PostToolUse

  • After tool completes

  • PermissionRequest

  • Permission dialog shown

Session events:

  • SessionStart

  • Session begins/resumes (matcher: startup/resume/clear/compact)

  • SessionEnd

  • Session ends

  • PreCompact

  • Before compaction (matcher: manual/auto)

Other events:

  • UserPromptSubmit

  • User submits prompt

  • Stop

  • Agent finishes

  • SubagentStop

  • Subagent finishes

  • Notification

  • Alerts sent (matcher: notification type)

Exit Codes

  • 0: Success. stdout shown in verbose mode. For UserPromptSubmit /SessionStart , stdout added to context.

  • 2: Block. stderr fed to Claude as error message. Blocks the action.

  • Other: Non-blocking error. stderr shown to user.

JSON Output

For advanced control, return JSON to stdout with exit code 0:

{ "continue": false, "stopReason": "Message shown when stopping" }

PreToolUse Control

{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "Auto-approved", "updatedInput": { "field": "modified value" } } }

Decisions: "allow" (bypass permission), "deny" (block), "ask" (prompt user)

PostToolUse Feedback

{ "decision": "block", "reason": "Explanation fed to Claude" }

Stop/SubagentStop Control

{ "decision": "block", "reason": "Must fix X before stopping" }

Hook Input

Hooks receive JSON via stdin:

{ "session_id": "abc123", "transcript_path": "/path/to/transcript.jsonl", "cwd": "/current/dir", "permission_mode": "default", "hook_event_name": "PreToolUse", "tool_name": "Write", "tool_input": { "file_path": "/path", "content": "..." } }

Common Patterns

Auto-format after edit:

{ "hooks": { "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs -I{} sh -c 'echo {} | grep -q "\.ts$" && npx prettier --write {}'" }] }] } }

Block dangerous commands:

{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": ""$CLAUDE_PROJECT_DIR"/.claude/hooks/validate-bash.py" }] }] } }

Inject context on prompt:

{ "hooks": { "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "echo '[REMINDER: Follow TDD]'" }] }] } }

Desktop notification:

{ "hooks": { "Notification": [{ "hooks": [{ "type": "command", "command": "osascript -e 'display notification "Claude needs input" with title "Claude Code"'" }] }] } }

Hook Scripts

For complex logic, use external scripts. UV single-file format works well:

#!/usr/bin/env -S uv run --script

/// script

dependencies = []

requires-python = ">=3.11"

///

import json import sys

data = json.load(sys.stdin) tool_input = data.get("tool_input", {})

Validation logic here

if should_block: print("Error message", file=sys.stderr) sys.exit(2)

sys.exit(0)

Anti-Patterns

  • Using exit code 2 without stderr message (Claude gets no feedback)

  • Forgetting to handle JSON parsing errors in scripts

  • Blocking without explaining why (Claude will retry the same thing)

  • Long-running hooks without timeout (default is 60s)

  • Modifying files in PreToolUse (use PostToolUse for modifications)

Reference

  • references/anthropic-hooks.md - Complete reference (input schemas, prompt hooks, MCP tools)

  • references/anthropic-hooks-guide.md - Quickstart and examples

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

hexagonal-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-worktrees

No summary provided by upstream source.

Repository SourceNeeds Review
General

refinement-loop

No summary provided by upstream source.

Repository SourceNeeds Review