CloudCodeTree LogoCloudCodeTree
AI NewsTutorialsAbout
CloudCodeTree Logo
CloudCodeTree
  • AI News
  • Tutorials
  • About
← Back to AI News
Claude Code Hooks: Auto-Format, Block Secrets, and Notify — Wired in settings.json

Claude Code Hooks: Auto-Format, Block Secrets, and Notify — Wired in settings.json

Chris Harper

3 min read

Jun 30, 2026 · 20:13 UTC

AI
Workflow
Claude Code
Best Practices

TL;DR: Claude Code hooks run any shell command before or after every tool call — wire them in .claude/settings.json to auto-format code, block unsafe operations, or send a notification when a multi-hour task completes.

Hooks are configured in .claude/settings.json (project-level, git-committed, shared by the team) or ~/.claude/settings.json (user-level, all projects). Each entry names an event, an optional tool matcher, and the command to run:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/secret-scan.sh" }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          { "type": "command", "command": "bash .claude/hooks/format.sh" }
        ]
      }
    ]
  }
}

The four hook events

EventFiresCan block?
PreToolUseBefore any tool callYes — exit 2 blocks the tool
PostToolUseAfter a tool succeedsNo
PostToolUseFailureAfter a tool errorsNo
StopWhen Claude finishes a turnNo

Hook input and exit codes

Every hook receives the tool's full JSON payload on stdin (read it with cat):

input="$(cat)"
# Extract the Bash command being run
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
# For Edit/Write hooks, use .tool_input.file_path instead

Exit codes:

  • 0 → proceed normally
  • 2 → block the tool; your stderr becomes Claude's feedback so it can adjust
  • anything else → proceed, error is logged

Pattern 1 — Block commits containing secrets

#!/usr/bin/env bash
set -euo pipefail
input="$(cat)"
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')

case "$cmd" in
  *"git commit"*|*"git push"*)
    diff="$(git diff --cached 2>/dev/null || true)"
    if printf '%s' "$diff" \
      | grep -Eq 'sk-ant-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}'; then
      echo "Blocked: secret detected in staged diff." >&2
      exit 2
    fi
    ;;
esac
exit 0

Wire as a PreToolUse hook with "matcher": "Bash". When Claude tries to commit, the hook checks the diff; if a key is found, it exits 2 — Claude sees the block reason and will offer to remove the secret before retrying.

Pattern 2 — Auto-format after every file edit

#!/usr/bin/env bash
set -euo pipefail
fp=$(cat | jq -r '.tool_input.file_path // empty')
[ -z "$fp" ] && exit 0

case "$fp" in
  *.py)           black "$fp" 2>/dev/null ;;
  *.ts|*.tsx|*.js) prettier --write "$fp" 2>/dev/null ;;
  *.go)            gofmt -w "$fp" ;;
esac
exit 0

Wire as PostToolUse with "matcher": "Write|Edit|MultiEdit". Claude's next read of that file sees the formatted version — no separate "please format" prompt needed.

Pattern 3 — Desktop notification when a long task finishes

#!/usr/bin/env bash
# macOS / Linux
osascript -e 'display notification "Task complete" with title "Claude Code"' \
  2>/dev/null \
|| notify-send "Claude Code" "Task complete" 2>/dev/null
exit 0

Wire as a Stop hook (no matcher — Stop has no tool to filter). After every session turn, the notification fires. Set timeout: 0 if the notification command might exceed the default 10-second limit.

Matchers

The matcher field is a regex matched against the tool name:

"Bash"                   # shell commands only
"Write|Edit|MultiEdit"   # any file-writing tool
"mcp__github__.*"        # all GitHub MCP tools

Omit matcher to match all tools for that event.

Project hooks (.claude/settings.json) and user hooks (~/.claude/settings.json) both run — they stack. To temporarily disable all hooks without deleting them, set "disableAllHooks": true in either file.

Sources: Hooks reference | Automate actions with hooks