All Agent Engineering

Spec file

Claude Code hooks: checks before a tool call

Use PreToolUse, matchers and exit codes to build an explainable command check. Source-based; not tested.

AI Nav CityWritten / Updated

A repeated check written only in a prompt still depends on the model choosing to run it. A command hook attaches that check to a specific lifecycle event. This page rejects one demonstration command to explain event input, matching and return values. It does not perform a publication or treat a simple string comparison as a complete shell command filter.

Verified
· Not tested by us
Applies to
  • Claude Code Based on Claude Code documentation read on 2026-11-03; behavior depends on the installed version and organization settings.[1]

File locations

PathScopeHow it is read
.claude/settings.jsonProject hook registration, with scripts stored in .claude/hooks/Claude Code executes registered commands when the matching event occurs.[1]

When to use

  • You need a deterministic check to run on a lifecycle event rather than an open-ended model judgment.[1]

When not to use

  • You expect PostToolUse to undo an external action or want to treat a string match as a complete shell parser.[1]

Choose before or after the action

PreToolUse runs before a tool call and can block it; PostToolUse handles work after a call. A matcher selects tool names such as Bash within that event. It does not infer your business condition. A command hook receives JSON on stdin, so its script must read fields belonging to the event it handles.[1]

Register a demonstration check

Example: a pre-call check for Bash
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{"type": "command", "command": "python3 .claude/hooks/demo-command-check.py"}]
    }]
  }
}

[1]

If hooks already exist, add the event or handler without replacing other events. This example assumes that the command runs from the project root and Python 3 is available. Confirm the script path before saving it and inspect registration with /hooks.[1]

Read input and return a decision

Example: reject only the exact demonstration command
import json
import sys

data = json.load(sys.stdin)
command = data.get("tool_input", {}).get("command", "").strip()
if command == "npm publish":
    print("Demo publication requires separate review.", file=sys.stderr)
    sys.exit(2)
sys.exit(0)

[1]

The documentation identifies exit code 2 as a blocking error whose effect depends on the event. With PreToolUse it blocks this call. The script matches only an exact string; additional arguments, shell wrappers and other publication paths are outside its coverage. It demonstrates the return protocol rather than a complete permission policy.[1]

Check the script before connecting the event

  • Feed simulated tool_input.command values to the script: npm publish should exit 2 and npm test should exit 0.
  • Keep shell startup noise out of stdout; place error explanations on stderr and distinguish script failures from deliberate blocks.
  • After integration, inspect /hooks and event logs. If it does not fire, check the event name, matcher, working directory and permissions.

[1]

The JSON parses, and the Python script was executed with the two simulated inputs. Claude Code and real hooks were not run, and npm publish was not executed. Validate the full event path in your own disposable project before relying on the hook.[1]

Sources

All links are official pages; verification dates are listed per source, and links point to the version checked.

  1. Anthropic · Automate actions with hooks - Claude Code Docs

Found an error? Tell us →