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" }
      ]
    }