explainx.ai0k
TrendingAI News TodayPathwaysSkills
Pricing
explainx.ai

Upskill in AI — 16 free pathways, live workshops & bootcamps, and 50+ courses from practitioners. Plus the skills, tools, and MCP servers to practice on.

follow us

follow on google

Add explainx.ai as a preferred source

corporate training

support@explainx.ai

get started

Find your pathTake Free Evaluation

community

Join the community

learn

mind: share how you thinkpathways — start freeworkshopsbootcampscoursescompare Explainxcertificationsmock testsexplainx universitycorporate traininglearn skills & mcp

discover

skillsmcp serversexplainx mcptoolsmdx readeragentsllmsdesignsdictionarypeopleagi trackerfelony benchranks

company

aboutvisionmissionteaminstructorsteach on explainxpartnershipscommunityhackathonscareers

content

daily AI newsstate of AI — live resultsblogreleasespromptsgeneratorsresource libraryfor LLMsexplainx.ai kids

solutions

all solutionsdeveloper upskillingmarketing upskillingproduct manager upskillingleadership upskilling

newsletter · weekly

Get AI news, tools, and insights in your inbox.

supportcontactprivacytermsdata rightshow we create contentsubmission guidelines

© 2026 AISOLO Technologies Pvt Ltd

explainx.ai

On this page

  • What Hooks Are
  • Where to Configure Hooks
  • Hook Types: PreToolUse and PostToolUse
  • The Hook Format in settings.json
  • Hook Exit Codes
  • Six Real-World Use Cases
  • User-Level vs. Project-Level Hooks
  • Testing Hooks with --debug
  • Security Considerations
  • Summary: Hook Decision Table
  • Exercise the hook with fixtures before trusting it
  • Keep automation small enough to understand
  • Related Reading on explainx.ai
← Back to blog

explainx / blog

Claude Code Hooks: Automate Actions Before and After Every Tool Call

Claude Code, Developer Tools, Automation, AI Agents

Part of Claude Code

Claude Code hooks let you run shell commands automatically before or after any tool call — Edit, Write, Bash, Read, and more. Learn how to configure PreToolUse and PostToolUse hooks in .claude/settings.json to auto-lint, block dangerous commands, log every action, and more.

Jun 12, 2026·10 min read·Yash Thakker
add explainx.ai
go deep
Claude Code Hooks: Automate Actions Before and After Every Tool Call

Update — August 22, 2026: Barehands uses the same lifecycle idea in a physical interface: local hooks can mirror an agent session into a visible ring state while the board remains a separate, allowlisted action surface.

If you have ever used Claude Code to edit a file and then immediately typed prettier --write or npm test in a separate terminal, you already know the pattern hooks are designed to eliminate. Claude Code hooks are shell commands that execute automatically at defined points in the tool-call lifecycle — no prompting, no post-edit rituals, no manual oversight.

This guide covers the complete hooks system: what hooks are, how to configure them, which exit codes matter, and six practical use cases you can copy today.

Update — July 13, 2026: For a maintained, cross-agent implementation of the destructive-command pattern, see our Destructive Command Guard guide, including the current Codex protocol caveat and fail-open limitations.

Weekly digest3.6k readers

Catch up on AI

Curated AI updates on agents, skills, and MCP — delivered to your inbox. Unsubscribe anytime.


What Hooks Are

A hook is a shell command that Claude Code runs before or after it executes one of its built-in tools. The built-in tools include:

  • Bash — running terminal commands
  • Edit — making changes to an existing file
  • Write — creating or overwriting a file
  • Read — reading a file's contents
  • WebFetch, WebSearch, and others

Every time Claude Code is about to invoke one of these tools, it can first run a hook you have defined. Every time Claude Code finishes invoking one of these tools, it can run a separate hook you have defined. The hook is just a shell command — it can be a one-liner, a script path, or a chained command.


Where to Configure Hooks

Hooks live in settings.json under the hooks key. There are two locations:

table · 2 cols
LocationScope
.claude/settings.json (project root)Applies to everyone on the project
~/.claude/settings.json (home directory)Applies to all projects for your user

Both files use the same schema. If you want your team to share a lint-on-edit rule, put it in the project-level file and commit it to the repository. If you want personal logging or notifications, put it in your user-level file.


Hook Types: PreToolUse and PostToolUse

This guide focuses on two tool-lifecycle events. Claude Code also has session, prompt, stop, and other events; see the current hook reference.

PreToolUse — runs before Claude executes the matched tool. For a command hook, exit 2 blocks the tool call. Other non-zero codes do not automatically block it. Structured decisions are another documented option.

PostToolUse — runs after Claude has already executed the matched tool. The exit code of a PostToolUse hook does not block anything. Use this for side effects: formatting, testing, logging, notifications.


The Hook Format in settings.json

The hooks key contains an object with PreToolUse and/or PostToolUse arrays. Each entry in the array specifies a matcher (which tool to watch) and a hooks array (the commands to run).

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'About to run bash'"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint --fix"
          }
        ]
      }
    ]
  }
}

The matcher selects tool names and supports regular-expression patterns such as Edit|Write. This guide uses command handlers; the current hooks system supports additional handler types. A command handler receives a JSON event on standard input, not a plain filename or command string. Parse the event before using its fields.


Hook Exit Codes

For PreToolUse hooks, exit codes carry enforcement weight:

  • Exit 0 — finish successfully without a blocking decision; normal permission checks still apply
  • Exit 2 — block a PreToolUse call and provide the stderr explanation
  • Other non-zero exits — report a hook error without automatically blocking the tool

For PostToolUse hooks, exit codes are informational only. The tool has already run. A failing PostToolUse hook will surface an error in the output but will not undo what was done.

This asymmetry is intentional. PreToolUse is for gates and guards. PostToolUse is for reactions.


Six Real-World Use Cases

1. Auto-Format Every File Edit

The most common use case. After Claude edits or writes any file, run Prettier automatically.

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

If your formatter is ESLint with --fix, swap the command:

json
{
  "type": "command",
  "command": "jq -r .tool_input.file_path | xargs -I {} npx eslint --fix --quiet -- {}"
}

These examples require jq and the project's formatter. For filenames containing newlines or a stronger path policy, use a script that parses JSON and calls the formatter with an argument array instead of xargs. Formatting can fail, so inspect its result.


2. Run Tests After File Changes

Every time Claude writes a file, run the test suite. This catches regressions before Claude moves on to the next step.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "npm test --silent"
          }
        ]
      }
    ]
  }
}

For large suites, use a project-owned script that parses the event and maps the edited path to an appropriate test command. Runner flags differ; do not pass the whole JSON event as a test pattern:

json
{
  "type": "command",
  "command": "bash .claude/hooks/test-changed-file.sh"
}

3. Log Every Bash Command for an Audit Trail

In security-sensitive environments, you need a record of every command Claude runs. A PreToolUse hook on Bash that appends to a log file gives you this without blocking anything.

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{event: .hook_event_name, tool: .tool_name, command: .tool_input.command}' >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

Create the log directory first and check write permissions. The event contains the command as JSON; do not assume an environment variable holds it. Commands may contain sensitive data, so redact before retaining or sharing logs.


4. Block Dangerous Commands

Use a PreToolUse hook on Bash to inspect the command before it runs and reject it if it matches a dangerous pattern.

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'CMD=$(jq -r .tool_input.command); if echo \"$CMD\" | grep -qE \"rm -rf /|dd if=|mkfs|shutdown|reboot\"; then echo \"Blocked: dangerous command detected\" >&2; exit 2; fi'"
          }
        ]
      }
    ]
  }
}

When this hook exits with code 2, Claude Code aborts the Bash call and surfaces the error message. A simple substring check can miss alternative spellings and can flag harmless quoted text. Use documented permissions and suitable isolation alongside any hook; this example is not a shell parser or a containment boundary.

For a more maintainable setup, move the logic into a script:

bash
#!/bin/bash
# ~/.claude/hooks/check-bash.sh
CMD=$(jq -r '.tool_input.command')
BLOCKED_PATTERNS=("rm -rf /" "dd if=" "mkfs" ":(){:|:&};:" "chmod -R 777 /")
for pattern in "${BLOCKED_PATTERNS[@]}"; do
  if echo "$CMD" | grep -qF "$pattern"; then
    echo "Blocked: matched dangerous pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0

Then reference it:

json
{
  "type": "command",
  "command": "bash ~/.claude/hooks/check-bash.sh"
}

5. Record Changes for a Reviewed Checkpoint

A hook can report changed paths after an edit, while the developer controls the commit:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "git diff --stat"
          }
        ]
      }
    ]
  }
}

Review the diff and stage the intended files explicitly. Automatically running git add -A after each write can include unrelated work from another contributor and can commit an incomplete change. A checkpoint should reflect a reviewed unit of work, not just the most recent tool event.


6. Send a Slack Notification When a Long Task Finishes

After Claude finishes a Bash command (which might have been a long build or deploy), send a Slack notification.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -X POST -H 'Content-type: application/json' --data '{\"text\":\"Claude Code finished a bash task.\"}' $SLACK_WEBHOOK_URL"
          }
        ]
      }
    ]
  }
}

Note: $SLACK_WEBHOOK_URL is read from your shell environment, not hardcoded in the JSON. Never put the raw webhook URL directly in settings.json.

Sort each job into the hook type that fits:

Play · pre or post?
Which hook type fits the job?
Run Prettier on every file Claude edits
1/6

User-Level vs. Project-Level Hooks

Project-level (.claude/settings.json):

  • Lives in the repository root
  • Committed to version control
  • Shared with every contributor
  • Best for: lint rules, test runners, safety guards your whole team should have

User-level (~/.claude/settings.json):

  • Lives in your home directory
  • Never committed
  • Applies to every Claude Code session on your machine
  • Best for: personal audit logs, Slack notifications, personal safety rules

When both files exist, Claude Code merges them. Project-level hooks and user-level hooks both run. There is no override — both lists execute.


Testing Hooks with --debug

Before relying on a hook in production, verify it works with the --debug flag:

bash
claude --debug

In debug mode, Claude Code prints each hook invocation — the matcher, the command, the exit code, and any stdout/stderr output. This is the fastest way to confirm your hook is being triggered and that the exit code is what you expect.

A common mistake: the hook command path is not in $PATH at the time Claude Code runs. If your hook fails silently, check whether the binary is accessible from a non-interactive shell (the environment Claude Code hooks run in).


Security Considerations

Hooks run with your OS user's full permissions. There is no sandboxing. A hook that runs curl or bash has the same access you do.

Two rules:

  1. Never put secrets in hook command strings. If your hook needs an API key or a webhook URL, read it from an environment variable (like $SLACK_WEBHOOK_URL above) or from a file outside the repository. The command string in settings.json can end up in logs, process listings, and version control history.

  2. Review third-party settings.json files. If you clone a repository that includes .claude/settings.json with hooks already defined, read those hooks before running Claude Code. A malicious hook in PreToolUse on Bash could exfiltrate data on every command.

The same common sense that applies to .github/workflows applies here.


Summary: Hook Decision Table

table · 4 cols
GoalHook TypeMatcherExit on block?
Auto-format filesPostToolUseEditWrite
Run tests after writePostToolUseWriteNo
Audit log all bashPreToolUseBashNo (exit 0)
Block dangerous commandsPreToolUseBashYes (exit 2)
Report checkpoint changesPostToolUseWriteNo
Slack notificationsPostToolUseBashNo

Hooks are one of the most underused features in Claude Code. They close the gap between "Claude made a change" and "the change is verified, formatted, and logged" — without any additional prompting or terminal switching on your part.

Update — September 4, 2026: Anthropic previewed a much deeper successor to shell-command hooks — Function Hooks, TypeScript middleware that can rewrite tool calls and intercept UI events, plus a shipped ant apply CLI for declaring Claude Managed Agents as code. That paragraph described the September preview. Check current documentation for the status of function hooks rather than treating a publication snapshot as a present-day availability statement.


Exercise the hook with fixtures before trusting it

Use a disposable repository and save representative event payloads. Start with a Write event naming a small file, then an Edit event naming a path containing spaces. Feed each fixture to the hook script directly and inspect stdout, stderr, and the exit status. This separates a script bug from a matcher or settings-loading problem. If the fixture works but a live edit does not trigger the hook, investigate configuration and event selection next.

For a blocking hook, test one allowed command and one command that the demonstration rule rejects. Confirm the rejected call never creates a sentinel file in the disposable directory. An error message alone is not proof of prevention. Also test missing fields and malformed input so you understand whether your script rejects, ignores, or crashes on an unexpected payload. Do not discover that behavior during a real destructive operation.

For a formatter, use a file with an obvious formatting change and confirm only that file changes. A tool may be invoked successfully while the formatter exits unsuccessfully because dependencies are absent or the syntax is invalid. Retain enough output to diagnose the failure. If a formatter edits a file after the agent writes it, make sure subsequent review inspects the actual final file rather than the earlier tool input.

Keep automation small enough to understand

Begin with one hook and a specific benefit, such as formatting a known file type. Measure the extra delay over several ordinary edits. A full test suite after every small write can make interactive work sluggish and may run against an unfinished set of changes. A short check after an edit and a larger verification step before a reviewed commit are often easier to interpret.

Treat notification delivery as a separate side effect. A failed webhook should have a clear result without implying that the code change failed, and a successful notification should not imply that the tests passed. Avoid putting command contents or private file data into messages merely because the event makes them available. Send the minimum information needed by the recipient.

Finally, document the hook's dependencies and failure behavior beside its configuration. A teammate should know which binary must exist, which paths are affected, and what a reported error means. Re-run the fixtures after changing the script or upgrading the tool. These checks make the hook an observable part of the workflow instead of an invisible command that everyone assumes is enforcing a policy.

Related Reading on explainx.ai

  • Claude Code: The Complete Developer Guide to AI-Assisted Coding
  • AI Agents and Automation: What Actually Works in 2026
  • Context Engineering and Clean Prompts for Better AI Output
  • Agent Markdown Files: The Complete Guide
  • Destructive Command Guard: Block Dangerous AI Agent Commands
  • SEO and GEO: Optimizing for AI Search Engines in 2026

Update — October 3, 2026: Mods are the in-process cousin of hooks; see how to build a Claude Code mod, a step-by-step tutorial with screenshots.

Spotted something out of date? Let us know.
Yash Thakker

Written by

Yash Thakker

Yash is an AI expert with over 300K learners. Join his workshops →

View Yash Thakker in People in AI →

Related posts

May 29, 2026

Introducing Dynamic Workflows in Claude Code: Quarter-Long Work in Days

Claude Code introduces dynamic workflows that run tens to hundreds of parallel subagents in a single session, handling complex tasks like codebase-wide migrations, bug hunts, and security audits that would normally take weeks.

Oct 15, 2026

Capsem: An Open-Source VM Sandbox for Claude Code, Gemini CLI and Codex

Capsem is an Apache-2.0 Rust project that boots each AI coding agent session in a fresh, air-gapped Linux VM and forces all traffic through an inspecting proxy. explainx.ai reads the README and site, flags the contradictions, and shows how to evaluate it.

Oct 15, 2026

Claude Code Desktop for Parallel Agents: Sidebar, Worktrees and Nested Sessions

Anthropic is pitching the Claude Code desktop app as a workbench for many agents at once. Here is what the docs confirm about parallel sessions, Git worktree isolation, the status sidebar and cross-session messaging, and what we could not confirm.