Limited founding-tier pricing $299/mo locked for life with code FOUNDING50 · Claim your spot →

IntermediateClaude Code

Automate your workflow with Claude Code hooks

Run a shell command on every Claude event — without writing a plugin.

Hooks let you wire shell commands to Claude Code lifecycle events: before a tool call, after an edit, on session end. This is the playbook for the four hook patterns that actually pay off.

9 min read
claude-codeautomationhooksworkflow

Hooks are the closest thing Claude Code has to a programmable assistant. They run shell commands at specific moments — before a tool fires, after a file is edited, when a session starts or stops. If a behavior should happen every time, hooks are how.

Where they live

Hooks are configured in ~/.claude/settings.json (personal, applies to all projects) or .claude/settings.json (project, checked in via git). Same shape:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx prettier --write \"$tool_path\"" }
        ]
      }
    ]
  }
}

That hook formats any file Claude edits or writes, automatically.

The four hook patterns worth knowing

1. Auto-format on save

Stop nagging Claude about formatting. Let the tools do it.

"PostToolUse": [{
  "matcher": "Edit|Write|MultiEdit",
  "hooks": [
    { "type": "command", "command": "npx prettier --write \"$tool_path\"" }
  ]
}]

The $tool_path env var holds whatever file Claude just touched. Pair with eslint, ruff, gofmt — whatever your team already runs on save.

2. Run tests on every meaningful change

When Claude finishes a code edit, kick off the relevant tests. Surface failures immediately so Claude can self-correct in the same turn.

"PostToolUse": [{
  "matcher": "Edit",
  "hooks": [
    { "type": "command", "command": "npm test -- --findRelatedTests \"$tool_path\"" }
  ]
}]

Best paired with Jest's --findRelatedTests (or pytest's similar logic) so you only run the tests touched by the change.

3. Block dangerous commands in sensitive directories

Hooks can return non-zero to block a tool call. Use this for guardrails: prevent Claude from rm -rf in production directories, prevent migrations from running outside staging.

"PreToolUse": [{
  "matcher": "Bash",
  "hooks": [
    { "type": "command", "command": "scripts/validate-bash-command.sh" }
  ]
}]

Your script reads $tool_command, exits 1 to block, prints to stderr to explain why. Claude reads the explanation and adjusts.

4. Notify when long jobs finish

Claude is happy to wait 8 minutes for a build. You aren't. A hook on Stop can ping you when a long-running session ends.

"Stop": [{
  "hooks": [
    { "type": "command", "command": "osascript -e 'display notification \"Claude session done\" with title \"Claude Code\"'" }
  ]
}]

(Mac.) Linux: notify-send. Windows: a PowerShell toast. Or pipe to your own Slack/Discord webhook.

What to avoid

  • Hooks that block silently. Always print to stderr explaining why you blocked. Otherwise Claude has to guess and the loop gets worse.
  • Hooks that take more than a few seconds. They run synchronously and slow down every tool call. Long jobs belong in background processes you trigger from a hook, not the hook itself.
  • Hooks that mutate state Claude is reasoning about. If your hook auto-fixes a file Claude is mid-edit on, you'll surprise Claude. Reformatting after the edit is fine; rewriting logic isn't.

Where to go next

  • See 10 Claude Code slash commands/hooks lists what's currently configured.
  • For a sharable hook config, drop one in .claude/settings.json at your repo root and commit it. Now the whole team gets the same guardrails.
  • Combine with CLAUDE.md setup — hooks are enforcement; CLAUDE.md is intent. Both compound.
Share:

Keep learning

Apply this with Waymaker

Get this article surfaced where you work

Inside Waymaker, this article shows up next to the right Signal page, so the lesson lands when you need it, not before.

No credit card required.