Skip to content

Claude Code Hooks

Claude Code Hooks

This project uses Claude Code's hook system to enforce development guardrails at the AI tool level. Hooks are shell scripts that run before Claude executes a tool (Edit, Write, Bash), and can block the action if it violates project rules.

Hook Overview

HookMatcherPurpose
prevent-main-commit.shBashBlocks git commit and git push on main/master
prevent-main-edit.shEdit, WriteBlocks file edits on main/master
prevent-generated-edit.shEdit, WriteBlocks edits to auto-generated files

How It Works

Hooks are configured in .claude/settings.json under hooks.PreToolUse. Each hook receives the tool input as the $TOOL_INPUT environment variable (JSON) and controls execution via exit codes:

Exit CodeBehavior
0Allow — tool execution proceeds
2Block — tool execution is rejected, stderr message shown to user

SSOT Protection: prevent-generated-edit.sh

The most notable hook enforces the SSOT principle by preventing Claude from directly editing generated files. It reads docsgen.yaml at runtime to build the list of protected files:

sh
# Extract output paths from docsgen.yaml
GENERATED=$(yq -r '.targets[].output' docsgen.yaml)

# Block if the target file matches any generated output
for gen in $GENERATED; do
  if [ "$REL_PATH" = "$gen" ]; then
    echo "BLOCKED: '$gen' is auto-generated. Edit template/ instead." >&2
    exit 2
  fi
done

This means the protection list stays in sync with docsgen.yaml automatically — no hardcoded file lists to maintain.

When blocked, Claude sees a message like:

BLOCKED: 'README.md' is auto-generated by docs-ssot.
Edit the source in template/ instead, then run 'make docs'.

Branch Protection: prevent-main-edit.sh / prevent-main-commit.sh

These hooks enforce GitHub Flow by preventing any file modifications or git commits directly on main or master. Claude is forced to create a feature branch first.

Adding New Hooks

  1. Create a shell script in .claude/hooks/:
    sh
    #!/bin/sh
    # Your validation logic here
    # Exit 0 to allow, exit 2 to block (message via stderr)
  2. Make it executable: chmod +x .claude/hooks/your-hook.sh
  3. Register it in .claude/settings.json:
    json
    {
      "matcher": "Edit",
      "hooks": [
        { "type": "command", "command": ".claude/hooks/your-hook.sh" }
      ]
    }