Auto-format after editing
PostToolUse hook on Edit|Write. Detects the extension of the modified file and applies the appropriate formatter.
INPUT=$(cat)FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path')EXT="${FILE##*.}"case "$EXT" in ts|tsx) npx prettier --write "$FILE" 2>/dev/null ;; py) black "$FILE" 2>/dev/null ;; go) gofmt -w "$FILE" 2>/dev/null ;; rs) rustfmt "$FILE" 2>/dev/null ;;esacexit 0Configure as async: true — formatting is cosmetic and should not block Claude.
macOS notification on completion
Stop hook. Triggers a macOS system alert with a contextual sound (success, error, waiting).
INPUT=$(cat)MESSAGE=$(echo "$INPUT" | jq -r '.message // "Done"')osascript -e "display notification \"$MESSAGE\" \ with title \"Claude Code\""afplay /System/Library/Sounds/Hero.aiff &exit 0Security: block dangerous commands
PreToolUse hook on Bash. Intercepts destructive patterns before execution.
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command')DANGEROUS=("rm -rf /" "dd if=" "DROP DATABASE" "git push --force.*main")for pattern in "${DANGEROUS[@]}"; do if [[ "$COMMAND" == *"$pattern"* ]]; then echo "BLOCKED: $pattern" >&2 exit 2 fidoneexit 0Exit code 2 blocks the action and surfaces the stderr message to Claude.
Git auto-stage after editing
PostToolUse hook on Edit|Write. Automatically prepares modified files for the next commit.
INPUT=$(cat)FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')[[ -z "$FILE" || "$FILE" == "null" ]] && exit 0git add "$FILE" 2>/dev/nullexit 0RTK auto-wrapper
PreToolUse hook on Bash. Detects commands optimizable by RTK and informs Claude of the available alternative.
The logic: if the command starts with git log, cargo test, pnpm list or verbose equivalents, suggest rtk <command> via stdout before letting it through.
Log all executed commands
PostToolUse hook on Bash. Useful for auditing and debugging long sessions.
COMMAND=$(cat | jq -r '.tool_input.command // empty')echo "$(date -u +%FT%T) | $COMMAND" \ >> ~/.claude/logs/commands.logexit 0Dispatcher pattern: a single entry point
Rather than multiplying entries in settings.json, a dispatch.sh script routes to specialized handlers based on the file or tool. Result: a single Edit|Write|Bash matcher in the config, with separately maintainable handlers in .claude/hooks/handlers/.
Output replacement: redact secrets before Claude sees them (v2.1.121)
PostToolUse now supports output replacement for ALL tools. If your hook prints a JSON with an "output" key, Claude receives that instead of the real tool result. Use this to strip secrets from file reads.
INPUT=$(cat)TOOL=$(echo "$INPUT" | jq -r '.tool_name')OUTPUT=$(echo "$INPUT" | jq -r '.tool_response.content // empty')
# Redact .env values before Claude sees themif [[ "$TOOL" == "Read" ]]; then CLEAN=$(echo "$OUTPUT" | sed 's/\(API_KEY=\).*/\1[REDACTED]/') echo "{\"output\": $(echo "$CLEAN" | jq -Rs .)}"fiexit 0Invoking an MCP tool from a hook (v2.1.118)
Hooks can use type: "mcp_tool" to call any connected MCP server without a shell script. Example: post a Slack notification via an MCP Slack server whenever a file is edited.
{ "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "mcp_tool", "server": "slack", "tool": "post_message", "input": { "channel": "#dev-alerts", "text": "Claude edited a file" } }] }]}Installation checklist
chmod +x .claude/hooks/my-hook.sh # Required# Test manually before activating:echo '{"tool_name":"Edit","tool_input":{"file_path":"test.ts"}}' \ | .claude/hooks/my-hook.shEnter your email to read the full card and get the complete PDF bundle.
All content is free and open-source. We just ask for your email.