claudekit / guides / hooks-automation
[ Guide · Advanced · 12 min ]

Automate Workflows with Hooks

updated

How to wire automation into Claude Code with hooks. Covers which events are available, where to configure them, and how to debug when a hook doesn't fire.

What hooks enable

Hooks add an automation layer to Claude Code without modifying its code. Common uses:

  • Auto-format files after writing
  • Block dangerous commands (rm -rf, git push --force)
  • Generate session reports on stop
  • Preserve key info before compaction
  • Ask an LLM “is this change safe?” and block on the answer

Configuration locations

FileScope
~/.claude/settings.jsonUser-global
.claude/settings.jsonProject (committed)
.claude/settings.local.jsonPersonal local (gitignored)

Basic structure

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_response.filePath // .tool_input.file_path' | { read -r f; prettier --write \"$f\"; } 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Common events

EventWhenUse case
PreToolUseBefore a tool runsBlock dangerous commands, permission checks
PostToolUseAfter a tool runsAuto-format, run tests
PostToolUseFailureAfter a tool failsError logging, notifications
UserPromptSubmitWhen user submitsPrompt analysis, context injection
StopSession endGenerate reports, cleanup
PreCompactBefore compactionPreserve key info separately
SessionStartSession startEnvironment check, intro message

Three hook types

1. command — run a shell command

{
  "type": "command",
  "command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt"
}

Receives the event JSON on stdin. Use jq to extract fields.

2. prompt — ask an LLM

{
  "type": "prompt",
  "prompt": "Decide if this command makes a destructive system change. Answer 'block' or 'allow': $ARGUMENTS"
}

The LLM answer drives block/allow. Runs on a small fast model.

3. agent — run an agent

{
  "type": "agent",
  "prompt": "Verify that unit tests were added alongside the file changes"
}

For complex verification. Higher token cost.

Example: block dangerous commands

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Decide if this shell command permanently deletes data or makes hard-to-reverse changes. Answer 'block' or 'allow': $ARGUMENTS"
          }
        ]
      }
    ]
  }
}

Example: auto-format after write

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_response.filePath // .tool_input.file_path' | { read -r f; case \"$f\" in *.ts|*.tsx|*.js|*.jsx|*.json|*.md) prettier --write \"$f\";; esac; } 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Per-extension formatter selection.

Example: stop-time report

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"systemMessage\": \"Session ended — review changed files before closing.\"}'"
          }
        ]
      }
    ]
  }
}

JSON output with systemMessage is shown to the user.

Debugging checklist

  1. JSON syntaxjq . settings.json to validate
  2. Matcher pattern — exact tool name (e.g. Write|Edit)
  3. Test the command directlyecho '{"tool_input":{"file_path":"test.ts"}}' | <hook command>
  4. Restart — new hooks need a Claude Code restart or one open of /hooks so the watcher picks them up
  5. Debug modeclaude --debug shows hook execution logs

Use hookify

hookify generates hooks from natural language. If hand-writing JSON feels heavy, draft with hookify first and apply the result to settings.json.

Next steps

§ 12

Frequently Asked Questions

frequently asked
§ 12.1
What are hooks?
Shell commands or LLM prompts that run automatically on specific events (before/after a tool call, session start/end, compaction, etc.). They add an automation layer to Claude Code without changing any code.
§ 12.2
Where do I configure them?
In the `hooks` field of `~/.claude/settings.json` (user-global), `.claude/settings.json` (project-shared), or `.claude/settings.local.json` (personal local).
§ 12.3
Which events are supported?
PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SessionStart, SessionEnd, PreCompact, PostCompact, and more. PreToolUse and PostToolUse are the most common for automation around tool calls.
§ 12.4
Can hooks use an LLM instead of running code?
Yes. Use `type: "prompt"` for LLM-based decisions, or `type: "agent"` to run a small agent for more complex verification.
§ 12.5
How do I debug a hook that isn't firing?
Most issues come from JSON syntax errors or matcher patterns that don't match. Run `claude --debug` for hook execution logs and `jq -e` to validate JSON. New hooks need a restart or one open of `/hooks` for the watcher to pick them up.