DS DevShelfHub Projects · AI tools
Claude Code Intermediate · 9 min read Page 11 of 25

Hooks — Automating Claude's Actions

By DevShelfHub

Hooks are event listeners for Claude's tool use. They let you run shell commands at precise moments in a session — before a tool runs, after it runs, when the session starts or ends — so formatting, linting, notifications, and quality gates happen automatically.

Series progress11 / 25
Claude Code hooks tutorial — PreToolUse, PostToolUse, and session automation

What hooks do

A hook runs a shell command at a specific point in Claude's workflow — think of them as event listeners for the agentic loop. Depending on configuration they run synchronously, blocking the next action until they finish, or asynchronously, firing off and letting Claude continue.

That distinction matters. A synchronous hook is a gate: if it fails, the action it guards is stopped. An asynchronous hook is a side effect: it notifies or logs without slowing anything down. Choosing the right mode for each job is the core skill of hook design.

Hook types

Five lifecycle events let you attach behavior at every meaningful phase of a session.

Hook Fires Typical use
PreToolUse Before a tool executes Block or validate an action — run tests before git push
PostToolUse After a tool executes Cleanup and notifications — format a file Claude just wrote
SessionStart When a session begins Load context, set up the environment, log start time
SessionEnd When a session ends Cleanup, logging, teardown of temp resources
Stop When Claude finishes responding Validation gates on the final state of the work

Setting up hooks in settings.json

Hooks are configured in .claude/settings.json under a top-level "hooks" key. Each event holds an array of rules; each rule has a matcher that scopes when it fires and a list of hooks commands to run.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": {"tool_name": "write_file", "file_paths": ["**/*.ts", "**/*.tsx"]},
        "hooks": [
          {"type": "command", "command": "npx eslint --fix ${file_path}"},
          {"type": "command", "command": "npx prettier --write ${file_path}"}
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": {"tool_name": "bash", "command_pattern": "git push"},
        "hooks": [
          {"type": "command", "command": "npm run typecheck && npm test"}
        ]
      }
    ],
    "SessionStart": [
      {
        "hooks": [
          {"type": "command", "command": "echo 'Session started at $(date)' >> ~/.claude/session.log"}
        ]
      }
    ]
  }
}

Here the PreToolUse hook is a gate — if typecheck or tests fail, the git push never happens. The PostToolUse hook is cleanup that lints and formats every TypeScript file Claude writes.

Practical hook patterns

A few rules cover most of what teams actually want automated. Auto-format after every TypeScript edit:

json
{"matcher": {"file_paths": ["**/*.ts"]}, "hooks": [{"command": "npx prettier --write ${file_path}"}]}

Notify Slack when Claude finishes a session:

json
{"type": "PostSessionEnd", "command": "curl -X POST $SLACK_WEBHOOK -d '{\"text\": \"Claude Code session finished\"}'"}

Run a security scan before any commit:

json
{"matcher": {"tool_name": "bash", "command_pattern": "git commit"}, "hooks": [{"command": "npm run security-scan"}]}

Environment variable expansion

Hooks receive context about the action that triggered them, exposed as variables you can interpolate into commands:

  • ${file_path} — the file the tool acted on
  • ${tool_name} — the tool that triggered the hook
  • ${working_directory} — the directory the session is running in

Use them to make hooks file-aware and context-sensitive — that's how prettier --write ${file_path} formats exactly the file Claude just edited rather than the whole tree.

Hooks for sub-agents

Hooks can also be defined directly in a sub-agent's frontmatter, scoping them to that agent's behavior only. This is ideal for specialized agents that need different quality gates — a security-review agent might run extra scans that the rest of your workflow doesn't need.

Pitfalls and best practices

Common mistakes

  • Slow hooks — auto-formatting on every edit adds up fast
  • Blocking hooks that fail and stop Claude from completing tasks
  • Shipping hooks to the team without testing them first

Best practices

  • Test hooks with edge cases before adding to shared settings
  • Use async hooks for notifications, sync hooks for quality gates
  • Keep hooks simple — complex logic belongs in scripts, not inline commands

Claude Code Hooks FAQ

What are Claude Code hooks?

Hooks are shell commands that Claude Code runs automatically at specific points in the agentic loop — before a tool runs (PreToolUse), after it runs (PostToolUse), when a session starts or ends (SessionStart, SessionEnd), and when Claude finishes responding (Stop). They let you automate formatting, linting, notifications, and quality gates without manual steps.

Where do you configure Claude Code hooks?

Hooks live in .claude/settings.json under a top-level "hooks" key. Each lifecycle event holds an array of rules, and each rule has a matcher that scopes when it fires plus a list of commands to run. You can also define hooks directly in a sub-agent's frontmatter to scope them to that agent only.

What is the difference between synchronous and asynchronous hooks?

A synchronous hook is a gate: it blocks the next action until it finishes, and if it fails the guarded action is stopped — useful for running tests before a git push. An asynchronous hook is a side effect: it fires off and lets Claude continue, which is ideal for notifications and logging that should not slow anything down.

What variables can hook commands use?

Hooks receive context about the action that triggered them as variables you can interpolate into commands: ${file_path} is the file the tool acted on, ${tool_name} is the tool that triggered the hook, and ${working_directory} is the directory the session runs in. These make hooks file-aware so a command like prettier --write ${file_path} formats only the file Claude just edited.

What are common mistakes when setting up hooks?

The most common pitfalls are slow hooks that auto-format on every edit and add up fast, blocking hooks that fail and stop Claude from completing tasks, and shipping hooks to a shared team config without testing them first. Keep hooks fast, use async mode for notifications and sync mode for quality gates, and move complex logic into scripts rather than inline commands.

Quick summary

  • Hooks run shell commands at lifecycle events: PreToolUse, PostToolUse, SessionStart, SessionEnd, Stop
  • Configure them in .claude/settings.json with a matcher plus a list of commands
  • Variables like ${file_path} make hooks context-aware
  • Use sync hooks as gates and async hooks for notifications; keep them fast and tested

Next, pair hooks with Claude Code skills for reusable workflows, or browse the full Claude Code tutorial series.