Skip to content

edgeless-ai/claude-code-hooks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-code-hooks

Drop-in automation hooks for Claude Code. Battle-tested across 200+ sessions.

Built by Edgeless Labs -- the hooks we wish Claude Code shipped with.

What are hooks?

Claude Code hooks are scripts that run automatically in response to events -- before/after tool calls, on session start/end, and more. They enforce conventions, prevent mistakes, and automate workflows without manual intervention.

Every hook receives a JSON payload on stdin and exits with a code that tells Claude Code what to do:

Exit code Meaning
0 Allow / continue
2 Block the operation (stderr message is shown to Claude)

Available Hooks

Hook Event Purpose
damage-control PreToolUse Blocks dangerous commands and writes to protected paths
no-force-push PreToolUse Prevents force pushes to main, master, production
security-scan PreToolUse Detects API keys and secrets before they reach disk
branch-naming PreToolUse Enforces type/description branch naming convention
verify-completion PostToolUse Warns when completion is claimed without test evidence
auto-commit-message PostToolUse Validates Conventional Commits format after each commit
lint-on-save PostToolUse Runs linter on the file that was just written
test-on-change PostToolUse Runs the corresponding test file after source changes
cost-tracker Stop Logs token usage and estimated cost per session to SQLite
session-logger Stop Appends a summary line to a session CSV log

Quick Start

Option 1: Install all hooks

git clone https://github.com/edgeless-ai/claude-code-hooks.git
cd your-project
../claude-code-hooks/install.sh

Option 2: Install a single hook

git clone https://github.com/edgeless-ai/claude-code-hooks.git
cd your-project
../claude-code-hooks/install.sh damage-control

Option 3: Copy manually

git clone https://github.com/edgeless-ai/claude-code-hooks.git

# Copy the hook config
cp claude-code-hooks/hooks/damage-control.json .claude/hooks/

# Copy the script
cp claude-code-hooks/scripts/damage-control.py .claude/hooks/scripts/
cp claude-code-hooks/scripts/damage-control-patterns.yaml .claude/hooks/scripts/

chmod +x .claude/hooks/scripts/*.py

Registering Hooks

After copying the files, register each hook in your .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Write|Edit|MultiEdit|Read",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/damage-control.py"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/no-force-push.py"
          },
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/branch-naming.py"
          }
        ]
      },
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/security-scan.py"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/verify-completion.py"
          },
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/auto-commit-message.py"
          }
        ]
      },
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/lint-on-save.py"
          },
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/test-on-change.py"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/cost-tracker.py"
          },
          {
            "type": "command",
            "command": "python3 .claude/hooks/scripts/session-logger.py"
          }
        ]
      }
    ]
  }
}

Hook Details

damage-control

Blocks dangerous operations before they execute. Configure rules in damage-control-patterns.yaml:

  • dangerous_commands.bash.block_patterns -- regex patterns matched against every Bash command. Catches rm -rf /, git push --force, DROP TABLE, curl | bash, and more.
  • paths.zero_access -- paths Claude can never read or write (credential files, SSH keys).
  • paths.read_only -- paths that can be read but never modified (node_modules, vendor, dist).
  • paths.no_delete -- paths that can be modified but never deleted (src, package.json).
  • allowlist -- path substrings that bypass all protections (e.g. /tmp/).

no-force-push

Blocks git push --force and git push -f to protected branches. Protected by default: main, master, production, prod, and any branch matching release/* or hotfix/*.

Configure PROTECTED_BRANCHES in the script.

security-scan

Scans file content before it is written. Detects:

  • OpenAI, Anthropic, AWS, Google API keys
  • GitHub PATs (classic and fine-grained)
  • Slack tokens, Telegram bot tokens
  • JWTs and private key blocks
  • Generic password=, secret=, api_key= assignments

Suppress a false positive with an inline comment: # noqa: secrets

branch-naming

Enforces <type>/<description> branch naming when Claude creates branches. Default allowed types: feat, fix, chore, docs, refactor, test, ci, perf, hotfix, release, experiment.

Configure ALLOWED_PREFIXES and NAME_PATTERN in the script.

verify-completion

Two modes:

Hook mode (PostToolUse/Bash): Detects when Claude claims a task is complete ("task complete", "all done", etc.) and warns if no verifiable evidence is present in the output (test counts, build success, coverage %).

CLI mode (standalone): Runs project-specific checks and exits 0 (PASS) or 1 (FAIL).

# CLI mode
python3 .claude/hooks/scripts/verify-completion.py --cli --type python
python3 .claude/hooks/scripts/verify-completion.py --cli --type typescript
python3 .claude/hooks/scripts/verify-completion.py --cli --type go --verbose

auto-commit-message

After each git commit, validates the commit subject against Conventional Commits format. If invalid, prints a suggested message Claude can use to amend. Does not block -- a bad message is better than no commit.

lint-on-save

After Write/Edit/MultiEdit, runs the appropriate linter on the changed file:

Extension Linter
.py ruff (preferred), flake8
.ts/.tsx/.js/.jsx eslint
.go golangci-lint, go vet
.rb rubocop
.sh/.bash shellcheck

Disable per-session: LINT_ON_SAVE_ENABLED=0

test-on-change

After Write/Edit/MultiEdit, finds the corresponding test file and runs it:

  • src/auth.py triggers tests/test_auth.py
  • components/Button.tsx triggers components/Button.test.tsx

If the modified file is itself a test file, runs it directly.

Disable per-session: TEST_ON_CHANGE_ENABLED=0

cost-tracker

On Stop, logs token usage and estimated USD cost to .claude/hooks/cost-tracker.db (SQLite). Pricing table is embedded in the script -- update it when Anthropic prices change.

# View cost report
python3 .claude/hooks/scripts/cost-tracker.py --report
python3 .claude/hooks/scripts/cost-tracker.py --report --days 30

Override the database path: COST_TRACKER_DB=/path/to/db

session-logger

On Stop, appends one line to .claude/session-log.csv:

timestamp,session_id,duration_secs,tool_count,project,notes
# View recent sessions
column -t -s, .claude/session-log.csv | tail -20

Writing Your Own Hook

Every hook follows the same pattern:

#!/usr/bin/env python3
import json, sys

def main() -> None:
    # Read the tool use event from stdin
    try:
        hook_data = json.loads(sys.stdin.read())
    except json.JSONDecodeError:
        sys.exit(0)  # Always fail open on parse errors

    tool_name: str = hook_data.get("tool_name", "")
    tool_input: dict = hook_data.get("tool_input", {})

    # PreToolUse: exit 2 to block, exit 0 to allow
    if should_block(tool_input):
        print("BLOCKED: reason here", file=sys.stderr)
        sys.exit(2)

    sys.exit(0)

    # PostToolUse / Stop: print JSON response
    # print(json.dumps({"continue": True}))

if __name__ == "__main__":
    main()

Key rules:

  • Always fail open: on parse errors or unexpected exceptions, exit 0 (allow). Hooks should never break the agent.
  • Print blocks to stderr: Claude Code displays stderr content to Claude.
  • Keep hooks fast: they run synchronously before/after every tool call.
  • Scrub secrets from logs: if you log tool inputs, redact credential patterns.

Repository Structure

claude-code-hooks/
  hooks/          # JSON configs (one per hook, declares event + matcher)
  scripts/        # Python implementation scripts
    damage-control.py
    damage-control-patterns.yaml
    no-force-push.py
    security-scan.py
    branch-naming.py
    verify-completion.py
    auto-commit-message.py
    lint-on-save.py
    test-on-change.py
    cost-tracker.py
    session-logger.py
  install.sh      # Installer
  README.md
  LICENSE

Requirements

  • Python 3.10+ (uses str | None type union syntax)
  • No third-party Python packages required -- stdlib only
  • Linter hooks require the relevant linter to be installed (optional)

More from Edgeless Labs

License

MIT -- see LICENSE

About

Drop-in automation hooks for Claude Code. Pre-commit validation, cost tracking, session lifecycle, and more.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors