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.

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.