Skip to content

Hook User Guide

📑 Table of Contents


Overview

The Hook feature allows you to insert custom scripts at critical points during AI Agent execution, enabling fine-grained control over Agent behavior. The Hook mechanism is fully compatible with the Claude Code Hooks specification, providing a powerful and flexible extension method.

Features

  • Multiple Event Support: Supports 7 key events (SessionStart, SessionEnd, PreToolUse, PostToolUse, UserPromptSubmit, Stop, PreCompact)
  • Tool Interception: Validate, modify, or block before and after tool execution
  • Context Injection: Dynamically inject additional context at different session stages
  • Parallel Execution: Multiple Hooks execute automatically in parallel for improved performance
  • Automatic Deduplication: Identical commands are automatically deduplicated to avoid repeated execution
  • Flexible Configuration: Supports regex matching, timeout control, project-level/user-level configuration
  • Session Tracking: Intelligently detects session changes to avoid duplicate SessionStart triggers
  • Safe and Reliable: Complete error handling and timeout mechanisms

Supported Hook Events

1. SessionStart - Session Startup

Trigger Timing: When a session starts (triggers only once per new session)

Trigger Logic:

  • The system determines if it's a new session by comparing conversationId
  • Multiple requests within the same session will not trigger repeatedly
  • Switching to a new session or clearing a session will trigger again

Use Cases:

  • Initialize project environment
  • Inject project-specific context
  • Set session-level configurations
  • Load project specifications and documentation

Matcher Field: source

  • startup - Initial startup (currently only this value is supported)

Input Data (stdin JSON):

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "SessionStart",
  "source": "startup"
}

Output Data (stdout JSON):

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Project uses TypeScript + React, prefer functional components"
  }
}

Example Configuration:

json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/session_start.py",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

2. SessionEnd - Session End

Trigger Timing: When a session terminates

Use Cases:

  • Clean up temporary resources
  • Save session state
  • Generate session reports

Matcher Field: reason

  • other - Session end (currently only this value is supported, including scenarios like switching sessions, deleting sessions, clearing sessions, etc.)

Input Data:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

Output Data:

json
{
  "continue": true,
  "systemMessage": "Session cleaned up, temporary files deleted"
}

3. PreToolUse - Before Tool Execution

Trigger Timing: Before any tool executes

Use Cases:

  • Validate tool parameters
  • Modify tool input
  • Block dangerous operations
  • Permission checks
  • Record audit logs

Matcher Field: tool_name

  • Examples: execute_command, write_to_file, read_file
  • Supports regex: write_to_file|replace_in_file
  • Match all: * or empty string

Input Data:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "PreToolUse",
  "tool_name": "execute_command",
  "tool_input": {
    "command": "npm install",
    "requires_approval": false
  }
}

Output Data - Allow Execution:

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow"
  }
}

Output Data - Modify Parameters:

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "Added --legacy-peer-deps parameter",
    "modifiedInput": {
      "command": "npm install --legacy-peer-deps",
      "requires_approval": false
    }
  }
}

Output Data - Block Execution:

json
{
  "continue": false,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Dangerous command detected: rm -rf /"
  }
}

Output Data - Request User Confirmation:

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",
    "permissionDecisionReason": "Detected git push --force, do you want to continue?"
  }
}

4. PostToolUse - After Tool Execution

Trigger Timing: After a tool completes execution

Use Cases:

  • Record tool execution logs
  • Post-process tool output
  • Trigger follow-up actions
  • Send notifications

Matcher Field: tool_name

Input Data:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "PostToolUse",
  "tool_name": "execute_command",
  "tool_input": {
    "command": "npm test"
  },
  "tool_response": {
    "exitCode": 0,
    "stdout": "All tests passed",
    "stderr": ""
  }
}

Output Data:

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Tests passed, you can continue development"
  }
}

5. UserPromptSubmit - User Input Submission

Trigger Timing: When user submits a message

Use Cases:

  • Pre-process user input
  • Add contextual information
  • Detect specific keywords
  • Input validation

Matcher: Not used (triggers for all submissions)

Input Data:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Help me implement a login feature"
}

Output Data:

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "Tip: The project has integrated JWT authentication library, recommend using it"
  }
}

Block Input:

json
{
  "continue": false,
  "stopReason": "Input contains sensitive information, blocked"
}

6. Stop - Agent Stops Responding

Trigger Timing: When Agent completes its response

Use Cases:

  • Provide feedback to Agent
  • Record execution status
  • Trigger follow-up tasks

Matcher: Not used

Input Data:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "Stop",
  "stop_hook_active": false
}

Output Data - Provide Feedback (exit code 2):

json
{
  "continue": false,
  "stopReason": "Please verify if the code passed unit tests"
}

7. PreCompact - Before Context Compaction

Trigger Timing: When context is about to be compacted

Use Cases:

  • Save important information
  • Provide compaction guidance
  • Backup complete context

Matcher Field: trigger

  • manual - User manually triggers /summarize
  • auto - Automatic compaction

Input Data:

json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.txt",
  "cwd": "/project/path",
  "hook_event_name": "PreCompact",
  "trigger": "auto",
  "custom_instructions": "Preserve all API design discussions"
}

Output Data (exit code 0):

json
{
  "continue": true,
  "hookSpecificOutput": {
    "hookEventName": "PreCompact",
    "additionalContext": "Important: Preserve database table structure design"
  }
}

Note: When exit code is 0, stdout content will be added as additional compaction guidance


Hook Script Specifications

Input Format

Hook scripts receive JSON-formatted input data via stdin.

Common Fields:

json
{
  "session_id": "Session ID",
  "transcript_path": "Path to conversation transcript file",
  "cwd": "Current working directory",
  "hook_event_name": "Event name"
}

Event-Specific Fields:

  • SessionStart: source
  • SessionEnd: reason
  • PreToolUse/PostToolUse: tool_name, tool_input, tool_response
  • UserPromptSubmit: prompt
  • PreCompact: trigger, custom_instructions
  • Stop: stop_hook_active

Output Format

Hook scripts return JSON-formatted output via stdout.

Basic Structure:

json
{
  "continue": true,
  "suppressOutput": false,
  "systemMessage": "Optional system message",
  "stopReason": "Reason for blocking (when continue=false)",
  "hookSpecificOutput": {
    "hookEventName": "Event name",
    "permissionDecision": "allow|deny|ask",
    "permissionDecisionReason": "Decision reason",
    "modifiedInput": {},
    "additionalContext": "Additional context"
  }
}

Field Descriptions:

  • continue: Whether to allow the operation to continue (false means block)
  • suppressOutput: Whether to hide stdout output
  • systemMessage: System message displayed to the user
  • stopReason: Reason for blocking
  • hookSpecificOutput: Event-specific output data

Exit Code Specifications

Exit CodeMeaningBehavior
0SuccessAllow operation to continue, stdout may be processed
1Non-blocking errorDisplay stderr as warning, allow to continue
2Blocking errorBlock operation, stderr passed to Agent/model
OtherNon-blocking errorSame as exit code 1

Special Rules:

  • PreToolUse: Exit code 2 blocks tool execution
  • Stop: Exit code 2 indicates feedback, stderr is injected into the next message
  • PreCompact: When exit code is 0, stdout serves as additional compaction guidance

Environment Variables

Hook scripts have access to the following environment variables during execution:

  • CLAUDE_PROJECT_DIR: Project root directory (Claude Code compatible)
  • CODEBUDDY_PROJECT_DIR: Project root directory (CodeBuddy specific)

Configuration Guide

Configuration File Locations

Priority (high to low):

  1. Project-level: <workspace>/.codebuddy/settings.json
  2. User-level: ~/.codebuddy/settings.json

Project-level configuration overrides user-level configuration.

Configuration File Structure

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "execute_command",
        "hooks": [
          {
            "type": "command",
            "command": "/absolute/path/to/script.py",
            "timeout": 10
          }
        ]
      },
      {
        "matcher": "write_to_file|replace_in_file",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/backup_script.sh",
            "timeout": 20
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/init.py",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Configuration Field Descriptions

matcher (Matcher)

Regular expression used to match specific conditions.

Syntax:

  • Empty string "" or "*": Match all
  • Single value: "execute_command"
  • Multiple values: "write_to_file|replace_in_file"
  • Regular expression: "read.*|search.*"

Match Targets for Different Events:

  • PreToolUse/PostToolUse: Matches tool_name
  • SessionStart: Matches source
  • SessionEnd: Matches reason
  • PreCompact: Matches trigger
  • UserPromptSubmit/Stop: Matcher not used

command (Command Path)

Path to the Hook script.

Requirements:

  • ✅ Absolute paths are recommended
  • ✅ Supports environment variables: "$CODEBUDDY_PROJECT_DIR/.codebuddy/hooks/script.py"
  • ✅ Can include interpreter: "python3 /path/to/script.py"
  • ⚠️ Ensure the script has execution permissions

timeout (Timeout)

Hook execution timeout, unit: seconds.

  • Default value: 60 seconds
  • Recommended settings: Adjust based on script complexity
    • Simple validation: 5-10 seconds
    • File operations: 15-30 seconds
    • Network requests: 30-60 seconds

Complete Examples

Example 1: Command Security Validation

Scenario: Block dangerous rm -rf commands

Hook Script (validate_command.py):

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

DANGEROUS_COMMANDS = ['rm -rf /', 'dd if=/dev/zero', 'mkfs']

def main():
    input_data = json.loads(sys.stdin.read())

    if input_data.get('tool_name') != 'execute_command':
        print(json.dumps({"continue": True}))
        return 0

    command = input_data.get('tool_input', {}).get('command', '')

    for dangerous in DANGEROUS_COMMANDS:
        if dangerous in command:
            output = {
                "continue": False,
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": f"Dangerous command detected: {dangerous}"
                }
            }
            print(json.dumps(output, ensure_ascii=False))
            return 0

    print(json.dumps({"continue": True}))
    return 0

if __name__ == "__main__":
    sys.exit(main())

Configuration:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "execute_command",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/validate_command.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Example 2: Smart Command Parameter Modification

Scenario: Automatically add --legacy-peer-deps parameter to npm install

Hook Script (modify_npm.py):

python
#!/usr/bin/env python3
import json
import sys
import re

def main():
    input_data = json.loads(sys.stdin.read())

    if input_data.get('tool_name') != 'execute_command':
        print(json.dumps({"continue": True}))
        return 0

    tool_input = input_data.get('tool_input', {})
    command = tool_input.get('command', '')

    # Check if it's npm install
    if re.match(r'^npm\s+(i|install)\b', command.strip()):
        # If --legacy-peer-deps is not present, add it
        if '--legacy-peer-deps' not in command:
            modified_command = command.strip() + ' --legacy-peer-deps'

            output = {
                "continue": True,
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "allow",
                    "permissionDecisionReason": "Automatically added --legacy-peer-deps parameter",
                    "modifiedInput": {
                        "command": modified_command,
                        "requires_approval": tool_input.get('requires_approval', False)
                    }
                }
            }
            print(json.dumps(output, ensure_ascii=False))
            return 0

    print(json.dumps({"continue": True}))
    return 0

if __name__ == "__main__":
    sys.exit(main())

Example 3: Automatic File Backup Before Modification

Scenario: Automatically create backups before modifying files

Hook Script (backup_files.py):

python
#!/usr/bin/env python3
import json
import sys
import os
import shutil
from datetime import datetime

def main():
    input_data = json.loads(sys.stdin.read())
    tool_name = input_data.get('tool_name', '')

    # Only process file write tools
    if tool_name not in ['write_to_file', 'replace_in_file']:
        print(json.dumps({"continue": True}))
        return 0

    tool_input = input_data.get('tool_input', {})
    file_path = tool_input.get('filePath')

    if not file_path or not os.path.exists(file_path):
        print(json.dumps({"continue": True}))
        return 0

    # Create backup directory
    project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
    backup_dir = os.path.join(project_dir, '.codebuddy', 'backups')
    os.makedirs(backup_dir, exist_ok=True)

    # Generate backup filename
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_name = f"{os.path.basename(file_path)}.{timestamp}.bak"
    backup_path = os.path.join(backup_dir, backup_name)

    # Create backup
    shutil.copy2(file_path, backup_path)

    output = {
        "continue": True,
        "systemMessage": f"Backed up to: {backup_path}"
    }
    print(json.dumps(output, ensure_ascii=False))
    return 0

if __name__ == "__main__":
    sys.exit(main())

Configuration:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "write_to_file|replace_in_file",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/backup_files.py",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

Example 4: Inject Project Context at Session Startup

Scenario: Automatically inject project configuration information when a session starts

Hook Script (session_start.py):

python
#!/usr/bin/env python3
import json
import sys
import os

def main():
    input_data = json.loads(sys.stdin.read())
    project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')

    # Read project configuration
    config_file = os.path.join(project_dir, '.codebuddy', 'project.json')
    project_info = ""

    if os.path.exists(config_file):
        with open(config_file, 'r') as f:
            config = json.load(f)
            project_info = f"""
Project Name: {config.get('name', 'Unknown')}
Tech Stack: {', '.join(config.get('tech_stack', []))}
Coding Standard: {config.get('coding_standard', 'Standard')}
"""

    output = {
        "continue": True,
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": f"""
Session started!
Project Directory: {project_dir}
Startup Source: {input_data.get('source', 'unknown')}
{project_info}
"""
        }
    }

    print(json.dumps(output, ensure_ascii=False))
    return 0

if __name__ == "__main__":
    sys.exit(main())

Example 5: Save Important Information Before Context Compaction

Scenario: Save complete conversation history before automatic compaction

Hook Script (save_context.py):

python
#!/usr/bin/env python3
import json
import sys
import os
import shutil
from datetime import datetime

def main():
    input_data = json.loads(sys.stdin.read())

    # Only process automatic compaction
    if input_data.get('trigger') != 'auto':
        print(json.dumps({"continue": True}))
        return 0

    project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
    transcript_path = input_data.get('transcript_path', '')

    if not transcript_path or not os.path.exists(transcript_path):
        print(json.dumps({"continue": True}))
        return 0

    # Create save directory
    save_dir = os.path.join(project_dir, '.codebuddy', 'context_history')
    os.makedirs(save_dir, exist_ok=True)

    # Save conversation history
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    save_path = os.path.join(save_dir, f'transcript_{timestamp}.txt')
    shutil.copy2(transcript_path, save_path)

    output = {
        "continue": True,
        "systemMessage": f"Context saved to: {save_path}"
    }
    print(json.dumps(output, ensure_ascii=False))
    return 0

if __name__ == "__main__":
    sys.exit(main())

Configuration:

json
{
  "hooks": {
    "PreCompact": [
      {
        "matcher": "auto",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/save_context.py",
            "timeout": 20
          }
        ]
      }
    ]
  }
}

Practical Guide

Quick Start - Configure Your First Hook in 5 Minutes

Step 1: Create Configuration File

bash
mkdir -p ~/.codebuddy
cat > ~/.codebuddy/settings.json << 'EOF'
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/env python3 -c \"import json,sys; print(json.dumps({'continue': True, 'hookSpecificOutput': {'hookEventName': 'SessionStart', 'additionalContext': 'Hook configured successfully!'}}))\"",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
EOF

Step 2: Restart Agent

Start a new session. If you see "Hook configured successfully!" the configuration is working.

Step 3: Create Your First Real Hook

bash
# Create Hook script directory
mkdir -p ~/.codebuddy/hooks

# Create test script
cat > ~/.codebuddy/hooks/my_first_hook.py << 'EOF'
#!/usr/bin/env python3
import json
import sys

def main():
    input_data = json.loads(sys.stdin.read())

    output = {
        "continue": True,
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": f"Welcome to Agent-Craft! Current project: {input_data.get('cwd', 'unknown')}"
        }
    }

    print(json.dumps(output, ensure_ascii=False))
    return 0

if __name__ == "__main__":
    sys.exit(main())
EOF

# Add execution permission
chmod +x ~/.codebuddy/hooks/my_first_hook.py

Step 4: Update Configuration File

json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "/Users/YOUR_USERNAME/.codebuddy/hooks/my_first_hook.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Advanced Debugging Tips

Tip 1: Use Log Files for Debugging

python
import sys

def debug_log(message):
    """Write to debug log without affecting stdout"""
    with open('/tmp/hook_debug.log', 'a') as f:
        f.write(f"{message}\n")

# Use in Hook script
debug_log(f"Received input: {json.dumps(input_data)}")

Tip 2: Validate JSON Output Format

bash
# Test if script output JSON is valid
echo '{"hook_event_name":"SessionStart"}' | python3 your_hook.py | jq .

Tip 3: Monitor Hook Execution

bash
# Watch Hook logs in real-time
tail -f ~/.codebuddy/logs/agent-craft.log | grep -i hook

Tip 4: Use Environment Variables to Pass Information

python
import os

# Get project directory in Hook
project_dir = os.environ.get('CODEBUDDY_PROJECT_DIR', '')
claude_dir = os.environ.get('CLAUDE_PROJECT_DIR', '')  # Claude Code compatible

Common Hook Patterns

Pattern 1: Whitelist Validation

python
ALLOWED_COMMANDS = [
    'npm install',
    'npm test',
    'git status',
    'git diff'
]

def is_allowed(command):
    return any(command.startswith(allowed) for allowed in ALLOWED_COMMANDS)

Pattern 2: Parameter Enhancement

python
def enhance_command(command):
    """Automatically add common parameters"""
    enhancements = {
        'npm install': ' --legacy-peer-deps',
        'git push': ' --dry-run',  # Safe mode
    }

    for prefix, suffix in enhancements.items():
        if command.startswith(prefix) and suffix not in command:
            return command + suffix

    return command

Pattern 3: Conditional Routing

python
def should_block(input_data):
    """Determine whether to block based on multiple conditions"""
    tool_name = input_data.get('tool_name')
    tool_input = input_data.get('tool_input', {})

    # Rule 1: Block deletion of important files
    if tool_name == 'delete_files':
        file_path = tool_input.get('target_file', '')
        if any(important in file_path for important in ['.git', 'package.json']):
            return True, "Cannot delete important files"

    # Rule 2: Block dangerous commands
    if tool_name == 'execute_command':
        command = tool_input.get('command', '')
        if 'rm -rf /' in command or 'dd if=' in command:
            return True, "Dangerous command detected"

    return False, None

Node.js Project Hook Configuration

json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.codebuddy/hooks/nodejs-init.js",
            "timeout": 15
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "execute_command",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codebuddy/hooks/npm-safety-check.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

Python Project Hook Configuration

json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codebuddy/hooks/python-env-check.py",
            "timeout": 10
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "write_to_file|replace_in_file",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.codebuddy/hooks/python-lint.py",
            "timeout": 20
          }
        ]
      }
    ]
  }
}

Best Practices

1. Script Development

  • Use Absolute Paths: Place Hook scripts in a fixed directory, reference using absolute paths
  • Error Handling: Ensure scripts have complete exception handling to avoid blocking the main flow
  • Fast Execution: Hooks should complete quickly, avoid time-consuming operations
  • Idempotency: Hooks may be called multiple times, ensure consistent results across multiple executions
  • Logging: Use sys.stderr for debug output, don't pollute stdout

2. Security

  • ⚠️ Input Validation: Always validate input data integrity
  • ⚠️ Whitelist Over Blacklist: Use whitelist mechanisms for permission control
  • ⚠️ Avoid Code Injection: Never directly execute user input
  • ⚠️ Least Privilege: Hook scripts should run with minimum necessary permissions

3. Performance Optimization

  • Set Reasonable Timeouts: Adjust timeout based on script complexity
  • Design for Parallelism: Avoid dependencies between Hooks, fully utilize parallel execution
  • Cache Results: Consider caching results for repeated calculations

4. Debugging Tips

Manually Test Hook Scripts:

bash
echo '{"hook_event_name":"PreToolUse","tool_name":"execute_command","tool_input":{"command":"npm install"}}' | \
  python3 /path/to/your_hook.py

Debug Output:

python
# Output debug information in Hook script
import sys

sys.stderr.write(f"[DEBUG] Processing command: {command}\n")
sys.stderr.flush()

5. Configuration Management

  • 📁 Project-Specific Hooks: Place in <workspace>/.codebuddy/, version control with project
  • 📁 Personal Hooks: Place in ~/.codebuddy/, reuse across projects

Performance Optimization Tips

1. Reduce Hook Execution Time

  • Use Fast Languages: Shell scripts typically start faster than Python
  • Avoid Repeated Work: Cache computation results
  • Async Processing: Use background tasks for non-critical operations
  • Early Exit: Determine early if processing is needed

Example:

python
# Bad practice: Load large file every time
def main():
    with open('huge_config.json', 'r') as f:
        config = json.load(f)  # Read every time
    # ... processing logic

# Good practice: Cache configuration
CONFIG_CACHE = None

def get_config():
    global CONFIG_CACHE
    if CONFIG_CACHE is None:
        with open('huge_config.json', 'r') as f:
            CONFIG_CACHE = json.load(f)
    return CONFIG_CACHE

2. Optimize Matcher Configuration

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "execute_command",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/fast_check.sh",
            "timeout": 3
          }
        ]
      },
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/general_check.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

3. Parallel Execution Considerations

  • Multiple Hooks execute in parallel, don't rely on execution order
  • Avoid file write conflicts between Hooks
  • Use file locks or atomic operations for shared resources
python
import fcntl

def safe_append_log(message):
    """Thread-safe log writing"""
    with open('/tmp/hook.log', 'a') as f:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
        f.write(message + '\n')
        fcntl.flock(f.fileno(), fcntl.LOCK_UN)

Security Best Practices

1. Input Validation

Never trust input data:

python
def validate_input(input_data):
    """Validate input data integrity"""
    required_fields = ['hook_event_name', 'session_id']

    for field in required_fields:
        if field not in input_data:
            raise ValueError(f"Missing required field: {field}")

    # Validate field types
    if not isinstance(input_data.get('tool_input'), dict):
        raise ValueError("tool_input must be a dictionary")

    return True

2. Command Injection Protection

Never directly execute user input:

python
# ❌ Dangerous: Direct execution
os.system(f"echo {user_input}")

# ✅ Safe: Use parameterized approach
import subprocess
subprocess.run(['echo', user_input], check=True)

3. Path Traversal Protection

python
import os

def safe_file_access(file_path, project_dir):
    """Ensure file path is within project directory"""
    abs_path = os.path.abspath(file_path)
    abs_project = os.path.abspath(project_dir)

    if not abs_path.startswith(abs_project):
        raise ValueError("Path traversal detected")

    return abs_path

4. Privilege Minimization

python
# Hook scripts should run with minimum privileges
# Avoid using sudo or root permissions

# ✅ Check privileges
if os.geteuid() == 0:
    print("Warning: Running as root is not recommended", file=sys.stderr)

Advanced Usage

Conditional Execution

Decide whether to process based on conditions in the Hook script:

python
def main():
    input_data = json.loads(sys.stdin.read())

    # Only process under specific conditions
    if not should_process(input_data):
        print(json.dumps({"continue": True}))
        return 0

    # Execute processing logic
    ...

Multi-Rule Combination

Implement multiple rules in a single Hook script:

python
def main():
    input_data = json.loads(sys.stdin.read())

    # Apply multiple rules
    for rule in RULES:
        if rule.matches(input_data):
            return rule.apply(input_data)

    # Default behavior
    print(json.dumps({"continue": True}))
    return 0

External Service Integration

Hooks can call external APIs or services:

python
import requests

def check_with_external_service(command):
    response = requests.post('https://api.example.com/validate',
                            json={'command': command},
                            timeout=5)
    return response.json()

FAQ

Q1: Hook is not being executed?

Checklist:

  1. ✅ Configuration file path is correct (settings.json in .codebuddy directory)
  2. hooks field is configured correctly, JSON format is valid
  3. matcher regex can match the target
  4. ✅ Hook script has execution permission (chmod +x script.py)
  5. ✅ Script path is correct (absolute paths recommended)
  6. ✅ Script has correct shebang on first line (#!/usr/bin/env python3)

Q2: Hook execution timeout?

Solutions:

  • Increase timeout configuration value
  • Optimize Hook script performance
  • Check for infinite loops or blocking operations

Q3: How to debug Hook scripts?

Debugging Steps:

  1. Use echo to manually pass test data
  2. Use sys.stderr for debug output in the script
  3. Verify JSON format is correct

Q4: Execution order of multiple Hooks?

Answer:

  • Hooks execute in parallel, execution order is not guaranteed
  • If sequential execution is needed, merge logic into a single Hook script
  • Identical commands are automatically deduplicated

Q5: Modified parameters not taking effect?

Check Points:

  • Ensure modifiedInput field is returned
  • Ensure permissionDecision is allow
  • Check if field names match tool parameters
  • Verify JSON format is correct
  • Ensure continue is true

Q6: SessionStart Hook triggers on every request?

Cause: SessionStart should only trigger once per new session

Solutions:

  • System tracks sessions via conversationId
  • Multiple requests within the same session will not trigger repeatedly
  • If issues persist, check if session ID changes in logs

Appendix

A. Complete HookInput Interface Definition

typescript
interface HookInput {
  // Common fields
  session_id?: string;              // Session ID
  transcript_path?: string;          // Conversation transcript path
  cwd?: string;                      // Current working directory
  hook_event_name: string;          // Hook event name

  // SessionStart specific (currently only 'startup' is supported)
  source?: 'startup';

  // UserPromptSubmit specific
  prompt?: string;                   // User input content

  // PreToolUse/PostToolUse specific
  tool_name?: string;                // Tool name
  tool_input?: Record<string, any>;  // Tool input parameters
  tool_response?: any;               // Tool response (PostToolUse only)

  // Stop specific
  stop_hook_active?: boolean;        // Whether Stop Hook is activated

  // PreCompact specific
  trigger?: 'manual' | 'auto';       // Trigger method
  custom_instructions?: string;      // Custom compaction instructions
}

B. Complete HookOutput Interface Definition

typescript
interface HookOutput {
  // Basic control
  continue?: boolean;                // Whether to continue execution (default true)
  stopReason?: string;               // Stop reason
  suppressOutput?: boolean;          // Whether to hide output
  systemMessage?: string;            // System message

  // Hook-specific output
  hookSpecificOutput?: {
    hookEventName: string;           // Hook event name

    // PreToolUse specific
    permissionDecision?: 'allow' | 'deny' | 'ask';
    permissionDecisionReason?: string;
    modifiedInput?: Record<string, any>;

    // SessionStart/UserPromptSubmit/PostToolUse specific
    additionalContext?: string;      // Additional context
  };
}

C. Environment Variables List

Environment VariableDescriptionExample Value
CODEBUDDY_PROJECT_DIRProject root directory/path/to/project
CLAUDE_PROJECT_DIRProject root directory (Claude Code compatible)/path/to/project

D. Exit Code Details

Exit CodeMeaningstdoutstderrBehavior
0SuccessProcessed as resultIgnoredContinue execution, may inject context
1WarningIgnoredDisplayed as warningContinue execution
2Block/FeedbackIgnoredPassed to AgentPreToolUse: Block execution
Stop: Provide feedback
OtherErrorIgnoredDisplayed as warningContinue execution

E. Common Tool Names List

File Operation Tools:

  • read_file - Read file
  • write_to_file - Write to file
  • replace_in_file - Replace file content
  • delete_files - Delete files
  • list_files - List files
  • search_file - Search files

Code Operation Tools:

  • search_content - Search content
  • read_lints - Read Lint errors

Execution Tools:

  • execute_command - Execute command
  • preview_url - Preview URL

Other Tools:

  • task - Create subtask
  • web_search - Web search
  • web_fetch - Fetch web content
  • ask_followup_question - Ask user

Summary

The Hook feature provides powerful extension capabilities, allowing you to insert custom logic at critical points of the AI Agent. By using Hooks properly, you can:

  • 🛡️ Enhance Security: Validate and block dangerous operations
  • 🔧 Automate Workflows: Intelligently modify parameters, automatically backup files
  • 📊 Monitor and Audit: Record tool execution logs
  • 🎯 Customize Behavior: Inject project-specific context

Start using the Hook feature to make your AI Agent smarter, safer, and more aligned with your project needs!

Happy Hooking! 🎣