explainx.ainewsletter3.5k
TrendingNewsPathwaysSkills
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

custom AI agents

[email protected]

get started

Find your pathTake Free Evaluation

learn

pathways — start freeworkshopsbootcampscoursescertificationsmock testsexplainx universitycorporate traininglearn skills & mcp

discover

skillsmcp serversexplainx mcptoolsagentsllmsdesignsagi trackerranks

company

aboutvisionmissionteaminstructorscommunityhackathonscareers

content

daily AI newsstate of AI — live resultsblogreleasespromptsgeneratorsresource librarydemofor LLMs

solutions

all solutionsdeveloper upskillingmarketing upskillingproduct manager upskillingleadership upskilling

More from us

InfloqInfluencer marketingBgBlurPrivacy-first blurOlly SocialSocial AI copilotCeptoryVideo intelligenceBgRemoverBackground removal

newsletter · weekly

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

contactsupportprivacytermsdata rightssubmission guidelines

© 2026 AISOLO Technologies Pvt Ltd

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
  • Related Reading on explainx.ai
← Back to blog

explainx / blog

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

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·7 min read·Yash Thakker
Claude CodeDeveloper ToolsAutomationAI Agents
go deep
Claude Code Hooks: Automate Actions Before and After Every Tool Call

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.5k 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:

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

There are exactly two hook types.

PreToolUse — runs before Claude executes the matched tool. If this hook exits with a non-zero code, Claude Code aborts the tool call entirely. This is how you block operations.

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 field is a string that matches the tool name exactly (case-sensitive): "Bash", "Edit", "Write", "Read", "WebFetch", etc. Multiple tools: use comma-separated matchers like "Bash,PowerShell" — a bug in versions before 2.1.191 caused those hooks to silently never fire; update Claude Code if multi-tool matchers do nothing (2.1.191 fix). The type field is always "command". The command field is the shell string that will be executed.


Hook Exit Codes

For PreToolUse hooks, exit codes carry enforcement weight:

  • Exit 0 — allow the tool call to proceed
  • Any non-zero exit — block the tool call; Claude Code aborts the operation and reports the failure

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": "prettier --write $(cat)"
          }
        ]
      },
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write $(cat)"
          }
        ]
      }
    ]
  }
}

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

json
{
  "type": "command",
  "command": "eslint --fix --quiet $(cat)"
}

The formatter runs immediately after each file operation, so Claude's next action always sees clean, formatted code.


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 test suites, scope it to the affected module rather than running everything:

json
{
  "type": "command",
  "command": "npm test -- --testPathPattern=$(cat) --silent"
}

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": "echo \"[$(date -u +%Y-%m-%dT%H:%M:%SZ)] BASH: $CLAUDE_TOOL_INPUT\" >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

This exits 0 every time (because it only appends and echoes), so it never blocks the command. It just records it. Review ~/.claude/audit.log after any session to see the full command history.


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=$(cat); if echo \"$CMD\" | grep -qE \"rm -rf /|dd if=|mkfs|shutdown|reboot\"; then echo \"Blocked: dangerous command detected\" >&2; exit 1; fi'"
          }
        ]
      }
    ]
  }
}

When this hook exits with code 1, Claude Code aborts the Bash call and surfaces the error message. Claude will see the block and either rephrase the approach or ask you to confirm explicitly.

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

bash
#!/bin/bash
# ~/.claude/hooks/check-bash.sh
CMD=$(cat)
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 1
  fi
done
exit 0

Then reference it:

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

5. Auto-Commit After Significant Edits

For workflows where you want a commit checkpoint after Claude finishes writing a file:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "git add -A && git diff --cached --quiet || git commit -m 'auto: claude write checkpoint'"
          }
        ]
      }
    ]
  }
}

The git diff --cached --quiet || guard prevents empty commits — it only commits if there are staged changes.

Use this with caution in projects with pre-commit hooks, since each Write will trigger the full pre-commit pipeline.


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.


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

GoalHook TypeMatcherExit on block?
Auto-format filesPostToolUseEdit, WriteNo
Run tests after writePostToolUseWriteNo
Audit log all bashPreToolUseBashNo (exit 0)
Block dangerous commandsPreToolUseBashYes (exit 1)
Auto-commit checkpointsPostToolUseWriteNo
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.


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
Yash Thakker

Written by

Yash Thakker

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

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.

Jul 22, 2026

Jack Dorsey's Buzz: Team Chat, AI Agents, and Git Hosting in One Nostr-Signed Workspace

Jack Dorsey announced Buzz on July 21, 2026 — a self-hostable, open-source workspace where humans and AI agents share one identity system across chat, Git, and workflows. Every message and code event is a signed Nostr event. Here's what's real, what's early, and why it matters for anyone running Claude Code, Codex, or Goose on a team.

Jul 12, 2026

Claude Code Desktop Browser: Built-In Web Browsing in the App (July 2026)

@ClaudeDevs ships Claude Code desktop browser — read, click, debug URLs sandboxed. Version 1.2581.0 July 10. explainx.ai setup, shortcuts, and developer reactions.