Claude Code hooks: three you can paste today

A hook is a shell command that Claude Code runs at a fixed point, such as after an edit or before a tool call. It happens every time, whether or not the model remembers.

Where a hook goes

Every event is a key inside one hooks object, so add a new event next to the existing ones, not over them. Type /hooks in a session to see what is configured.

1. A notification when Claude needs you

The Notification event fires when Claude is waiting for input or permission. On Linux:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
          }
        ]
      }
    ]
  }
}

On Windows, swap the command for this one. It opens a dialog box, which can appear behind your terminal:

"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""

On macOS use osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'. The empty matcher fires on every notification type. Use permission_prompt to fire only when Claude is waiting for you to approve something, or idle_prompt when it finished and you have not typed for about a minute.

2. Format files after Claude edits them

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

The hook receives the event as JSON on stdin. jq pulls out the path of the edited file and Prettier rewrites it. The matcher limits it to the file-editing tools. You need jq installed. When the hook succeeds it prints nothing, so check the file, not the conversation. A PostToolUse hook runs after the edit, so it cannot undo anything.

3. Block edits to protected files

Save this as .claude/hooks/protect-files.sh:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

for pattern in ".env" "package-lock.json" ".git/"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

On macOS and Linux run chmod +x .claude/hooks/protect-files.sh. Then register it in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

On Windows, paths come with backslashes, so normalise them to forward slashes before matching, as Anthropic's version of this script does.

How a hook answers

Hook, rule or CLAUDE.md?

A line in CLAUDE.md is a request. A permission rule is enforced by the program. A PreToolUse hook can do the same with your own logic, and its deny holds in every permission mode, including bypassPermissions, according to Anthropic's documentation.

Limits

Check it works

  1. Add one hook at a time to .claude/settings.local.json in a scratch project.
  2. Run /hooks and confirm it is listed under the right event.
  3. Trigger it on purpose: ask Claude to edit a .env file, or to add a badly formatted line.

This follows Anthropic's hooks guide as we read it in October 2026. That page is the authority, and we are not affiliated with Anthropic. We wrote this from the documentation and have not tested every example on every system.

If you want a second pair of eyes on your CLAUDE.md and settings, the Tune-Up Kit is a checklist for exactly that.

See also: Work toward a goal with /goal.