Skip to main content

claude-hooks

Simple CLI tool for configuring Claude Code hooks in your projects.

What This Does

claude-hooks helps you quickly add hooks to your Claude Code project. Instead of manually:

  • Creating .sh hook scripts
  • Making them executable
  • Editing .claude/settings.local.json

Just run: claude-hooks add <hook-name>

Installation

Recommended (with uv):

uv tool install claude-hooks-template

Or with pip:

pip install claude-hooks-template

Usage

Navigate to your Claude Code project directory (must have .claude/ directory) and run:

# Install all hooks at once
claude-hooks install

# Or add hooks one at a time:

# Add hook for Edit tool
claude-hooks add on_edit

# Add hook for TodoWrite tool
claude-hooks add on_todo_complete

# Add hook for Stop event
claude-hooks add on_stop

# Add hook for SessionStart event
claude-hooks add on_start

# Add hook for Bash tool
claude-hooks add on_bash

# Add hook for git commits (filter in script)
claude-hooks add on_git_commit

# Add hook for AskUserQuestion tool
claude-hooks add on_ask_user

# Add hook for Write tool
claude-hooks add on_write

# Add hook for MultiEdit tool
claude-hooks add on_multiedit

# Add hook for Task tool (subagent completion)
claude-hooks add on_task_complete

# Add hook for Read tool
claude-hooks add on_read

# Add hook for user prompt submission
claude-hooks add on_user_prompt

# Add hook for plan mode exit
claude-hooks add on_plan

# Add hook for pre-compaction (runs before context compaction)
claude-hooks add on_precompact

# Add hook for periodic snapshots
claude-hooks add on_snapshot

What It Does

When you run claude-hooks add <hook-name>:

  1. Creates hook script: .claude/hooks/<hook-name>.sh with basic template
  2. Makes it executable: chmod +x automatically applied
  3. Updates settings: Adds hook configuration to .claude/settings.local.json
  4. Shows summary: Displays hook event and matcher info

Example

$ claude-hooks add on_edit
Created hook script: .claude/hooks/on_edit.sh
Added hook to settings.local.json
  Event: PostToolUse
  Matcher: Edit

Edit .claude/hooks/on_edit.sh to customize hook behavior

Available Hooks

Hook Name Claude Event Matcher Description
on_edit PostToolUse Edit Triggers after Edit tool completes
on_todo_complete PostToolUse TodoWrite Triggers after TodoWrite tool completes
on_stop Stop (none) Triggers when agent stops responding
on_start SessionStart (none) Triggers when session starts
on_bash PreToolUse Bash Triggers before Bash tool executes
on_git_commit PreToolUse Bash Triggers before Bash tool executes (filter for git commit in script)
on_ask_user PreToolUse AskUserQuestion Triggers before AskUserQuestion tool executes
on_write PostToolUse Write Triggers after Write tool completes
on_multiedit PostToolUse MultiEdit Triggers after MultiEdit tool completes
on_task_complete PostToolUse Task Triggers after Task tool completes
on_read PreToolUse Read Triggers before Read tool executes
on_user_prompt UserPromptSubmit (none) Triggers when user submits a prompt
on_plan PreToolUse ExitPlanMode Triggers before ExitPlanMode tool executes
on_precompact PreCompact (none) Triggers before context compaction begins
on_snapshot PreToolUse (none) Triggers periodically to save conversation snapshots

Hook Script Templates

Each generated hook script includes comprehensive documentation:

  • Input JSON Schema: All fields hook receives from Claude Code
  • Output JSON Schema: Required/optional fields hook must return
  • Exit Code Documentation: What each exit code does
  • Practical Examples: Real jq parsing examples you can copy-paste

Example: PreToolUse Hook (on_plan, on_read, on_bash)

#!/bin/bash
# on_plan - Triggers before ExitPlanMode tool executes
#
# INPUT JSON SCHEMA:
# {
#   "session_id": "string",
#   "transcript_path": "string",
#   "cwd": "string",
#   "permission_mode": "default|plan|acceptEdits|bypassPermissions",
#   "hook_event_name": "PreToolUse",
#   "tool_name": "string (e.g., 'Read', 'Edit', 'Bash')",
#   "tool_input": {} (tool-specific parameters)
# }
#
# OUTPUT JSON SCHEMA:
# {
#   "hookSpecificOutput": {
#     "hookEventName": "PreToolUse",
#     "permissionDecision": "allow|deny|ask",
#     "permissionDecisionReason": "string (optional)",
#     "updatedInput": {} (optional - modify tool parameters)
#   },
#   "continue": true|false (optional, default: true),
#   "stopReason": "string (optional)",
#   "suppressOutput": true|false (optional, default: false),
#   "systemMessage": "string (optional)"
# }
#
# EXIT CODES:
#   0 - Normal success (allow tool execution)
#   2 - Block tool execution and show stderr to Claude
#   Other - Error condition
#
# EXAMPLES:
#
#   # Allow tool execution:
#   echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow"}}'
#
#   # Block tool execution:
#   echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Restricted file"}}'
#
#   # Parse specific fields:
#   TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
#   FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

INPUT=$(cat)

# Default: allow tool execution
echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow"}}'

exit 0

Example: PostToolUse Hook (on_edit, on_write, on_todo_complete)

#!/bin/bash
# on_edit - Triggers after Edit tool completes
#
# INPUT JSON SCHEMA:
# {
#   "session_id": "string",
#   "transcript_path": "string",
#   "cwd": "string",
#   "permission_mode": "default|plan|acceptEdits|bypassPermissions",
#   "hook_event_name": "PostToolUse",
#   "tool_name": "string (e.g., 'Edit', 'Write', 'TodoWrite')",
#   "tool_input": {} (tool-specific parameters that were used),
#   "tool_response": {} (tool-specific response/result)
# }
#
# OUTPUT JSON SCHEMA:
# {
#   "decision": "block|undefined (optional)",
#   "reason": "string (required if decision is 'block')",
#   "hookSpecificOutput": {
#     "hookEventName": "PostToolUse",
#     "additionalContext": "string (optional - added to Claude's context)"
#   },
#   "continue": true|false (optional, default: true),
#   "stopReason": "string (optional)",
#   "suppressOutput": true|false (optional, default: false),
#   "systemMessage": "string (optional)"
# }
#
# EXIT CODES:
#   0 - Normal success
#   2 - Show stderr to Claude for processing
#   Other - Error condition
#
# NOTE: Tool has already executed when this hook runs!
#
# EXAMPLES:
#
#   # Add context about what was changed:
#   FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
#   echo '{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Modified: '"$FILE_PATH"'"}}'

INPUT=$(cat)

# Default: do nothing
echo '{}'

exit 0

After generating a hook, edit the .sh file to add your custom logic. All the documentation you need is in the generated file!

Settings Format

Hooks are added to .claude/settings.local.json in this format:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/on_edit.sh"
          }
        ]
      }
    ]
  }
}

Requirements

  • Python 3.10+
  • Claude Code project with .claude/ directory
  • jq (optional, for parsing JSON in hook scripts)

Development

Setup

# Clone repository
git clone https://github.com/stevennevins/claude-hooks.git
cd claude-hooks

# Install dependencies
uv sync --group dev

Running Tests

# All tests
uv run pytest

# Unit tests only
uv run pytest tests/unit/ -v

# With coverage
uv run pytest --cov=claude_hooks

Code Quality

# Format code
uv run ruff format claude_hooks/ tests/

# Lint code
uv run ruff check claude_hooks/ tests/

# Type check
uv run pyright claude_hooks/

Releases

This project uses automated releases via GitHub Actions. When changes are merged to main, conventional commits determine version bumps:

  • fix: → patch version (0.1.0 → 0.1.1)
  • feat: → minor version (0.1.0 → 0.2.0)
  • BREAKING CHANGE or feat!:/fix!: → major version (0.1.0 → 1.0.0)

Manual releases can also be triggered:

# Using GitHub CLI
gh api repos/stevennevins/claude-hooks/dispatches \
  -f event_type=release

# Or use GitHub UI: Actions → Dispatch Release → Run workflow

Why This Tool?

Grug tired of manual hook setup. Make simple tool that:

  • Create hook script fast
  • Update settings automatically
  • Work every time same way
  • No remember JSON structure

Simple better than complex.

License

MIT

Metadata

Release files for claude-hooks-template 1.6.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for claude-hooks-template 1.6.4
File Size Uploaded
claude_hooks_template-1.6.4.tar.gz 43.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for claude-hooks-template 1.6.4
File Interpreter ABI Platform
claude_hooks_template-1.6.4-py3-none-any.whl Python 3 none any Details

Total release size: 55.1 kB

Release files / claude_hooks_template-1.6.4.tar.gz

Download URL claude_hooks_template-1.6.4.tar.gz
Size 43.3 kB
Tags Source
SHA-256 checksum
How to use checksums
c2ad05b7eda43179d0d2a29f48e0b4484b586e2afba033c3951e248948318a19
BLAKE2b-256 checksum
How to use checksums
c4dcae9a623f3664942491f50a9c7a84440b753f2524273a60fd9d3dc2ca5885
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2025.

Transparency log

Release files / claude_hooks_template-1.6.4-py3-none-any.whl

Download URL claude_hooks_template-1.6.4-py3-none-any.whl
Size 11.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d05c8e25621f54bb1927487409201b1b0e5438f28d8b9248c83c89a7cef352c
BLAKE2b-256 checksum
How to use checksums
289296ff0486d1628d953c513da47e77f4c67626c2966ef6c6c598951025c30b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

1.6.4 This release

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page