Advanced 12 min

Automate Workflows with Hooks

Updated

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

Frequently Asked Questions

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.

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).

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.

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.

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.