| .claude | ||
| cmd | ||
| internal | ||
| .gitignore | ||
| CLAUDE.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| README.md | ||
| test_debug.txt | ||
| test_debug2.txt | ||
| test_debug3.txt | ||
| test_debug4.txt | ||
| test_debug5.txt | ||
| test_debug6.txt | ||
Claude Code Hooks
A Go binary that handles Claude Code hooks for validating and modifying tool usage. This tool makes it easy to write custom hooks as simple Go functions.
Installation
go build -o hooks
./hooks install
This will configure your ~/.claude/settings.json to use this binary for all hook events.
Usage
Writing Custom Hooks
Hooks are simple Go functions that follow this signature:
func MyHook(ctx context.Context, input hooks.HookInput) (hooks.HookOutput, error) {
// Your logic here
return hooks.HookOutput{Decision: "continue"}, nil
}
Register your hook in an init function:
func init() {
// Register for specific tools
hooks.RegisterHook(hooks.EventPreToolUse, "Bash", ValidateBashCommand)
// Use regex patterns
hooks.RegisterHook(hooks.EventPreToolUse, "Write|Edit", PreventSensitiveEdits)
// Match all tools
hooks.RegisterHook(hooks.EventPostToolUse, "*", LogEverything)
}
Example: Block Dangerous Commands
func BlockRmRf(ctx context.Context, input hooks.HookInput) (hooks.HookOutput, error) {
bashInput, err := input.GetBashInput()
if err != nil {
return hooks.HookOutput{Decision: "approve"}, nil
}
if strings.Contains(bashInput.Command, "rm -rf /") {
return hooks.HookOutput{
Decision: "block",
Reason: "Cannot execute rm -rf on root directory",
}, nil
}
return hooks.HookOutput{Decision: "approve"}, nil
}
Example: Add Context to Prompts
func AddGitInfo(ctx context.Context, input hooks.HookInput) (hooks.HookOutput, error) {
branch := getCurrentGitBranch()
return hooks.HookOutput{
Decision: "continue",
AdditionalContext: fmt.Sprintf("Current git branch: %s", branch),
}, nil
}
Example: Modify Tool Parameters
func ForceNonInteractiveSudo(ctx context.Context, input hooks.HookInput) (hooks.HookOutput, error) {
bashInput, err := input.GetBashInput()
if err != nil {
return hooks.HookOutput{Decision: "approve"}, nil
}
if strings.Contains(bashInput.Command, "sudo") && !strings.Contains(bashInput.Command, "sudo -n") {
return hooks.HookOutput{
Decision: "approve",
ModifiedParameters: map[string]interface{}{
"command": strings.ReplaceAll(bashInput.Command, "sudo", "sudo -n"),
},
}, nil
}
return hooks.HookOutput{Decision: "approve"}, nil
}
Built-in Hooks
This package includes comprehensive hook implementations for all Claude Code events:
Pre Tool Use
- Bash Command Validator: Blocks dangerous commands using regex patterns
- Sensitive File Protection: Prevents editing
.env,.aws/credentials, etc.
Post Tool Use
- Tool Usage Logger: Logs all tool executions with structured logging
- Go Imports: Automatically runs
goimports -won modified Go files
User Prompt Submit
- Project Context: Adds git branch and project type information
Notification
- System Notifications: Logs notification events for debugging and monitoring
Stop
- Session Completion: Logs when main Claude agent finishes responding
SubagentStop
- Subagent Completion: Logs when Task tool subagents finish responding
PreCompact
- Context Compaction: Handles before context window compaction (manual/auto)
Git Notes Integration
This hooks system automatically attaches Claude conversation context to git commits using git notes. When you run git commit commands, the system captures relevant conversation context and stores it alongside your commits.
What Gets Stored
Each git note contains structured JSON data with:
- Session ID: Unique identifier for the Claude session
- Timestamp: When the commit was made
- Conversation Excerpt: Recent user prompts and tool interactions
- Tools Used: List of tools used during the conversation
- Commit Context: Details about the git command and output
- Claude Version: Which version of Claude created the commit
Example Usage
Let's say you're working on a project and ask Claude to implement a feature:
# You: "Add user authentication to the login form"
# Claude uses various tools (Edit, Write, Bash) to implement the feature
# Then creates a commit:
git commit -m "Add user authentication with password validation
- Implement bcrypt password hashing
- Add login form validation
- Create user session management
- Add error handling for failed logins"
The hook automatically captures the conversation context and attaches it to the commit.
Viewing Conversation Context
To view the conversation context attached to any commit:
# View notes for the latest commit
git notes --ref=claude-conversations show HEAD
# View notes for a specific commit
git notes --ref=claude-conversations show abc1234
# View notes in git log (one-liner)
git log --show-notes=claude-conversations --oneline
# View detailed notes in git log
git log --show-notes=claude-conversations -1
Example Output
{
"session_id": "claude_session_20250121_143022",
"timestamp": "2025-01-21T14:30:45Z",
"conversation_excerpt": "Recent user prompts:\n- Add user authentication to the login form\n- Make sure to use bcrypt for password hashing\n\nTool interactions:\n- Edit: components/LoginForm.jsx\n- Write: utils/auth.js\n- Bash: npm install bcrypt",
"tools_used": ["Edit", "Write", "Bash"],
"commit_context": "Git command: git commit -m 'Add user authentication with password validation'\nResult: [main abc1234] Add user authentication with password validation",
"claude_version": "claude-sonnet-4-20250514"
}
Configuration
Customize the git notes behavior by creating .claude/notes.json:
{
"enabled": true,
"max_excerpt_length": 5000,
"max_prompts": 2,
"include_tool_output": false,
"notes_ref": "claude-conversations",
"exclude_patterns": ["password", "token", "key", "secret"]
}
Privacy Controls
The system includes built-in privacy protections:
- Automatically filters sensitive patterns (passwords, tokens, keys)
- Limits excerpt length to prevent excessive data storage
- Only includes conversation context from the current session
- Configurable exclusion patterns
- Option to disable entirely (
"enabled": false)
Sharing Git Notes
Git notes are stored locally by default. To share them with your team:
# Push notes to remote repository
git push origin refs/notes/claude-conversations
# Pull notes from remote repository
git fetch origin refs/notes/claude-conversations:refs/notes/claude-conversations
# Configure automatic notes fetching
git config remote.origin.fetch '+refs/notes/*:refs/notes/*'
Testing Hooks
Test individual hooks by piping JSON:
echo '{
"event": "pre_tool_use",
"tool": "Bash",
"tool_use_request": {
"tool": "Bash",
"parameters": {
"command": "rm -rf /"
}
}
}' | ./hooks run
Enable debug logging:
./hooks run --debug
Hook Events
pre_tool_use: Before any tool executionpost_tool_use: After successful tool executionuser_prompt_submit: Before processing user promptsstop: When main agent finishessubagent_stop: When subagent finishesnotification: For permission requestspre_compact: Before context compaction
Configuration
Hooks are configured in ~/.claude/settings.json:
{
"hooks": [
{
"events": ["pre_tool_use", "post_tool_use"],
"matchers": [".*"],
"cmds": ["/path/to/hooks run"]
}
]
}
Security Warning
⚠️ USE AT YOUR OWN RISK: Hooks execute automatically when Claude Code runs tools. Ensure your hooks are thoroughly tested and secure.
Development
To add new hooks:
- Create a new file in
internal/handlers/ - Write your hook function
- Register it in an
init()function - Rebuild and reinstall:
go build && ./hooks install
Uninstalling
./hooks install --uninstall
This removes all hook configurations from your Claude settings.