AI Automation

Claude Code Hooks: auto-format, block dangerous commands, enforce tests — no reminders needed

TL;DR

A hook is a shell command (or HTTP, prompt…) that runs on events like PreToolUse, PostToolUse, Stop. Exit code 2 in PreToolUse blocks the tool call and feeds the reason to Claude; in Stop it forces Claude to keep going. Configure in .claude/settings.json, inspect with /hooks.

You can tell Claude Code in CLAUDE.md to “always format code after editing”, “never run rm -rf”, “run the tests before saying you’re done”. The model will comply… most of the time. Hooks turn those reminders into guarantees: a shell command (or HTTP call, prompt or agent) that runs automatically at a specific point in Claude Code’s lifecycle and can block an action or force Claude to keep working.

How hooks work

A hook has three parts:

  • Event: when it runs, e.g. PreToolUse (before a tool is used) or Stop (when Claude wants to stop).
  • Matcher: a filter, e.g. only the Bash tool, or Edit|Write.
  • Handler: what to do — run a shell command, call an HTTP endpoint, call an MCP tool, ask a model (prompt) or spawn a checking subagent.

Claude Code sends the event data as JSON on the hook’s stdin (session_id, cwd, tool_name, tool_input…). The hook answers with an exit code or JSON on stdout.

Commonly used events

EventRuns whenCan block?
SessionStartA session starts/resumesNo — used to add context
UserPromptSubmitYou submit a promptYes
PreToolUseBefore Claude uses a toolYes — allow, deny, ask, or rewrite input
PostToolUseAfter a tool finishesNo (it already ran) — but can send feedback
NotificationClaude Code sends a notification (e.g. awaiting approval)No
Stop / SubagentStopClaude / a subagent is about to end its turnYes — keeps it working
PreCompactBefore conversation compactionYes
TaskCompleted, TeammateIdleTasks / teammates in agent teamsYes
SessionEndThe session endsNo

There are many more, such as ConfigChange, FileChanged, PermissionRequest and PreModelSwitch — type /hooks in Claude Code to see configured hooks and where they come from.

Exit codes — the part that matters most

Exit codeMeaning
0Success. If stdout is a single JSON object, its fields are applied.
2Blocking error (for events that can block). Stderr is fed back to Claude as the reason.
Other (e.g. 1)Non-blocking error: the action proceeds and you only see a “hook error” notice.

The most common beginner mistake: using exit 1 to block — it doesn’t block. Mandatory rules need exit 2. For Stop, exit 2 means “you’re not done, keep going”.

Where to configure them

LocationScope
~/.claude/settings.jsonAll projects on your machine
.claude/settings.jsonProject — commit it for the whole team
.claude/settings.local.jsonProject, your machine only (gitignored)
Managed settingsOrganisation-wide
Plugins, skill/subagent frontmatterWhile the plugin is enabled / while the skill or subagent runs

Example 1: Auto-format after every edit

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

The hook reads JSON from stdin, extracts the edited file path and runs Prettier. Claude no longer needs to remember to format.

Example 2: Block rm

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "if": "Bash(rm *)",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
      }]
    }]
  }
}
#!/bin/bash
# .claude/hooks/block-rm.sh  (remember chmod +x)
echo "Deleting files with rm is not allowed. Ask the user first." >&2
exit 2

The if field uses permission-rule syntax (Bash(git *), Edit(*.ts)) to narrow the matcher. Note that if is best-effort; for a hard block, use a permission deny rule rather than relying on a hook alone.

Example 3: Don’t let Claude stop while tests are red

#!/bin/bash
# Hook for the Stop event
if ! npm test --silent > /tmp/test.log 2>&1; then
  echo "Tests are still failing. Read /tmp/test.log and fix them before finishing." >&2
  exit 2
fi
exit 0

This turns your “Definition of Done” into a rule a machine can check.

Example 4: Deny with a reason via JSON

Instead of exit 2, a PreToolUse hook can print JSON for finer control:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Direct writes to the production database are not allowed"
  }
}

permissionDecision accepts allow, deny, ask or defer, optionally with updatedInput to modify parameters before execution. The JSON must be the only thing on stdout — a greeting from your shell profile is enough to break parsing.

Other handler types

  • http: POSTs the event JSON to a URL — handy for central logging or alerts.
  • mcp_tool: calls a tool on a configured MCP server.
  • prompt: asks a model for a quick single-turn judgement (30-second default timeout).
  • agent: spawns a subagent with Read/Grep/Glob to verify conditions (experimental).

Command hooks time out after 600 seconds by default. $CLAUDE_PROJECT_DIR points to the project root — use it to call scripts by absolute path.

Security and control

  • Hooks run arbitrary commands with your permissions — review hooks shipped with plugins, skills and subagents before enabling them.
  • Project hooks follow the workspace-trust rules.
  • Organisations can use allowManagedHooksOnly to allow only admin-provided hooks.
  • Temporarily disable all hooks with "disableAllHooks": true.
  • Debugging: claude --debug or /debug logs full hook stderr and output.

How to apply it

  • Developers: Start with two cheap, high-value hooks: format after edits, and block rm / git push --force.
  • Tech Leads: Put hooks in the repo’s .claude/settings.json and review them like CI config. A Stop hook that runs tests and lint is the cheapest way to cut “AI says done but the build is red” PRs.
  • PMs: Every rule “the team keeps forgetting” (update the changelog, never touch old migrations…) is a hook candidate. Automate the checklist instead of repeating it in every review.

Sources & references

  1. Hooks reference — Claude Code Docs