Tools
Claude Code, jq
Price
Included (jq is free)
Time
15 min
Level
Terminal, intermediate

A CLAUDE.md file gives the AI instructions, and Claude usually follows them. Usually. For an invoice you have already sent, “usually” is not enough: an issued invoice must not change. Hooks solve this. Anthropic describes them as “deterministic” control: a given action always happens, instead of relying on the model to remember it.

How it works

A hook lives in a settings file, settings.json. It ties a moment (an “event”) to a command. The most useful moments:

  • PreToolUse: just before an action by Claude (editing a file, running a command). This hook can block it.
  • PostToolUse: just after a successful action, for example to reformat a file.
  • Notification: when Claude is waiting for your answer or permission.
  • SessionStart: when a session starts.

The matcher field says which actions the hook applies to: Edit|Write targets file edits. To block, the command must exit with code 2. Careful: exit code 1 does not block, it only produces a warning.

To start: get notified when the AI is waiting

A first, risk-free hook from the docs: a macOS notification whenever Claude needs you. Add it to ~/.claude/settings.json (create the file if it does not exist):

~/.claude/settings.json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

If nothing shows up, open System Settings, Notifications, and allow “Script Editor”, the app these notifications go through.

Block edits to your invoices

  1. Check that jq is installedjq reads the data Claude Code passes to the hook. If the first command does not answer, install it with Homebrew.
    Terminal
    jq --version
    brew install jq
  2. Create the check scriptIn your business folder, save this file as .claude/hooks/protect-files.sh. Adapt the list of protected patterns:
    protect-files.sh
    #!/bin/bash
    # Blocks Claude Code from editing certain files
    INPUT=$(cat)
    FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
    PROTECTED=("invoices/" ".env" ".git/")
    
    for pattern in "${PROTECTED[@]}"; do
      if [[ "$FILE" == *"$pattern"* ]]; then
        echo "Blocked: $FILE is protected ($pattern). Ask the user first." >&2
        exit 2
      fi
    done
    
    exit 0
  3. Make it executableWithout this step, Claude Code cannot run it.
    Terminal
    chmod +x .claude/hooks/protect-files.sh
  4. Test it by handSend the script a fake edit request. It should print the “Blocked” message, then the code 2.
    Terminal
    echo '{"tool_name":"Edit","tool_input":{"file_path":"invoices/INV-2026-001.md"}}' | .claude/hooks/protect-files.sh
    echo $?
  5. Register the hookAdd this block to .claude/settings.json at the root of the same folder. If it already has a hooks key, add PreToolUse next to the other events instead of replacing everything.
    .claude/settings.json
    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": "Edit|Write",
            "hooks": [
              {
                "type": "command",
                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
              }
            ]
          }
        ]
      }
    }
  6. Check it in Claude CodeType /hooks: your hook appears under PreToolUse. Then ask Claude to add a line to an invoice: the edit is refused before it happens, and Claude receives your message so it can adjust its approach.

Where to put a hook

  • ~/.claude/settings.json: all your folders, on this computer only.
  • .claude/settings.json: one folder, shareable with your team.
  • .claude/settings.local.json: one folder, just for you.

A blocking PreToolUse hook applies in every permission mode, even when you let Claude act without asking. That is what makes it a real guardrail.

Limits

  • This hook watches the file-editing tools. A terminal command run by Claude (rm, mv) goes through the Bash tool and is not covered: you would need a second hook on Bash, or a deny permission rule.
  • If the hook does not fire: check /hooks, the chmod +x, and that the JSON is valid (no trailing comma, no comments).
  • Nothing extra to pay: a “command” hook runs on your computer, without calling the model.

Security. A hook runs commands with your full user permissions: it can read, change or delete any of your files. Read and test every hook before adding it, and be wary of hooks sitting in the .claude/ folder of a project you got from elsewhere. Claude Code waits until you trust the folder before running them in an interactive session: do not grant that trust blindly.

Sources

Want AI agents that work on your quotes and invoices with guardrails like these from day one? That is what I build as AI agents.