Làm chủ Claude Code trong một tuần (Phần 7): Hooks

· 10 min read

Đây là phần thứ bảy trong series "Làm chủ Claude Code trong một tuần". Trong bài này, chúng ta sẽ tìm hiểu Hooks — event-driven automation cho Claude Code.

Hooks là gì?

Hooks là event-driven shell commands tự động execute khi Claude Code events xảy ra. Hooks cho phép bạn "hook into" lifecycle của Claude Code để thực hiện các actions như formatting, validation, notification.

┌────────────────────────────────────────────────────────────────┐
│                          HOOKS                                  │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Claude Code Event                                            │
│          │                                                     │
│          ▼                                                     │
│   ┌─────────────────┐     ┌─────────────────┐                 │
│   │  PreToolUse     │────►│ format-code.sh  │                 │
│   │  (Write file)   │     │ (Auto-format)   │                 │
│   └─────────────────┘     └─────────────────┘                 │
│          │                                                     │
│          ▼                                                     │
│   ┌─────────────────┐                                         │
│   │   Tool executes │                                         │
│   └─────────────────┘                                         │
│          │                                                     │
│          ▼                                                     │
│   ┌─────────────────┐     ┌─────────────────┐                 │
│   │  PostToolUse    │────►│ security-scan.sh│                 │
│   │  (Write done)   │     │ (Scan for issues)│                 │
│   └─────────────────┘     └─────────────────┘                 │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

Hook Types

Claude Code có 4 loại hooks với 25 events:

1. Tool Hooks

Event Khi nào Use case
PreToolUse Trước khi tool chạy Validation, formatting
PostToolUse Sau khi tool hoàn thành Scanning, logging
PostToolUseFailure Khi tool fail Error logging, recovery
PermissionRequest Khi cần permission Custom permission logic

2. Session Hooks

Event Khi nào Use case
SessionStart Session bắt đầu Setup, initialization
SessionEnd Session kết thúc Cleanup, summary
Stop User stop Save state
StopFailure Stop fail Recovery
SubagentStart Subagent bắt đầu Logging
SubagentStop Subagent kết thúc Cleanup

3. Task Hooks

Event Khi nào Use case
UserPromptSubmit User gửi prompt Validation, logging
TaskCompleted Task hoàn thành Notification
TaskCreated Task mới tạo Tracking
TeammateIdle Teammate idle Workload balance

4. Lifecycle Hooks

Event Khi nào Use case
ConfigChange Config thay đổi Reload
CwdChanged Directory thay đổi Context update
FileChanged File thay đổi Auto-refresh
PreCompact Trước compact Save important context
PostCompact Sau compact Restore context
WorktreeCreate Worktree mới Setup
WorktreeRemove Worktree xóa Cleanup
Notification Notification Custom handling
InstructionsLoaded Instructions loaded Validation
Elicitation Claude asks question Custom prompting
ElicitationResult Answer received Logging

Cấu hình Hooks

settings.json Location

Global:

~/.claude/settings.json

Project:

.claude/settings.json

Cấu trúc cấu hình

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": ["~/.claude/hooks/format-code.sh"]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": ["~/.claude/hooks/security-scan.sh"]
      }
    ],
    "SessionStart": [
      {
        "hooks": ["~/.claude/hooks/session-start.sh"]
      }
    ],
    "TaskCompleted": [
      {
        "hooks": ["~/.claude/hooks/notify-team.sh"]
      }
    ]
  }
}

Matcher Options

Matcher Description Example
"Write" File write operations Format before write
"Read" File read operations Log file access
"Bash" Shell commands Validate commands
"*" All tools Global logging
"Edit" File edits Backup before edit

Cài đặt Hooks

Step 1: Tạo thư mục

mkdir -p ~/.claude/hooks

Step 2: Copy hooks từ claude-howto

cp claude-howto/06-hooks/*.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/*.sh

Step 3: Cấu hình settings.json

# Tạo hoặc edit settings.json
cat > ~/.claude/settings.json << 'EOF'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": ["~/.claude/hooks/format-code.sh"]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": ["~/.claude/hooks/security-scan.sh"]
      }
    ]
  }
}
EOF

Ví dụ Hooks

1. Auto-format Code

File: ~/.claude/hooks/format-code.sh

#!/bin/bash
# Auto-format code before writing

# Get file path from environment
FILE_PATH="$CLAUDE_TOOL_INPUT_PATH"

if [ -z "$FILE_PATH" ]; then
    exit 0
fi

# Detect file type and format
case "$FILE_PATH" in
    *.php)
        # Format PHP with Pint or PHP-CS-Fixer
        if command -v pint &> /dev/null; then
            pint "$FILE_PATH" 2>/dev/null
        elif command -v php-cs-fixer &> /dev/null; then
            php-cs-fixer fix "$FILE_PATH" --quiet 2>/dev/null
        fi
        ;;
    *.js|*.ts|*.jsx|*.tsx)
        # Format JavaScript/TypeScript with Prettier
        if command -v prettier &> /dev/null; then
            prettier --write "$FILE_PATH" 2>/dev/null
        fi
        ;;
    *.py)
        # Format Python with Black
        if command -v black &> /dev/null; then
            black "$FILE_PATH" 2>/dev/null
        fi
        ;;
    *.go)
        # Format Go
        if command -v gofmt &> /dev/null; then
            gofmt -w "$FILE_PATH" 2>/dev/null
        fi
        ;;
esac

exit 0

2. Security Scan

File: ~/.claude/hooks/security-scan.sh

#!/bin/bash
# Scan for security issues after file write

FILE_PATH="$CLAUDE_TOOL_INPUT_PATH"
ISSUES_FOUND=0

if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
    exit 0
fi

# Check for hardcoded secrets
check_secrets() {
    local patterns=(
        "password\s*=\s*['\"]"
        "api_key\s*=\s*['\"]"
        "secret\s*=\s*['\"]"
        "AWS_ACCESS_KEY"
        "PRIVATE_KEY"
    )
    
    for pattern in "${patterns[@]}"; do
        if grep -iE "$pattern" "$FILE_PATH" > /dev/null 2>&1; then
            echo "⚠️  Potential hardcoded secret found: $pattern"
            ISSUES_FOUND=1
        fi
    done
}

# Check for SQL injection (basic)
check_sql_injection() {
    # Check for string concatenation in SQL
    if grep -E "(SELECT|INSERT|UPDATE|DELETE).*\\\$" "$FILE_PATH" > /dev/null 2>&1; then
        echo "⚠️  Potential SQL injection: variable in SQL string"
        ISSUES_FOUND=1
    fi
}

# Check for debug statements
check_debug() {
    local debug_patterns=(
        "console\.log"
        "var_dump"
        "print_r"
        "dd\("
        "dump\("
        "debugger;"
    )
    
    for pattern in "${debug_patterns[@]}"; do
        if grep -E "$pattern" "$FILE_PATH" > /dev/null 2>&1; then
            echo "⚠️  Debug statement found: $pattern"
            ISSUES_FOUND=1
        fi
    done
}

# Run checks
check_secrets
check_sql_injection
check_debug

if [ $ISSUES_FOUND -eq 1 ]; then
    echo ""
    echo "Security scan found issues in: $FILE_PATH"
    echo "Please review before committing."
fi

exit 0

3. Log Bash Commands

File: ~/.claude/hooks/log-bash.sh

#!/bin/bash
# Log all bash commands executed by Claude

LOG_FILE="${HOME}/.claude/logs/bash-$(date +%Y-%m-%d).log"
mkdir -p "$(dirname "$LOG_FILE")"

# Get command from environment
COMMAND="$CLAUDE_TOOL_INPUT_COMMAND"

if [ -n "$COMMAND" ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $COMMAND" >> "$LOG_FILE"
fi

exit 0

Cấu hình:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": ["~/.claude/hooks/log-bash.sh"]
      }
    ]
  }
}

4. Pre-commit Validation

File: ~/.claude/hooks/pre-commit.sh

#!/bin/bash
# Run tests before committing

# Only run for git operations
if [[ "$CLAUDE_TOOL_INPUT_COMMAND" != *"git commit"* ]]; then
    exit 0
fi

echo "🧪 Running pre-commit checks..."

# Run tests
if [ -f "phpunit.xml" ]; then
    ./vendor/bin/phpunit --stop-on-failure
    if [ $? -ne 0 ]; then
        echo "❌ Tests failed. Commit blocked."
        exit 1
    fi
elif [ -f "package.json" ]; then
    npm test
    if [ $? -ne 0 ]; then
        echo "❌ Tests failed. Commit blocked."
        exit 1
    fi
fi

echo "✅ Pre-commit checks passed."
exit 0

5. Team Notification

File: ~/.claude/hooks/notify-team.sh

#!/bin/bash
# Notify team when task is completed

WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"

if [ -z "$WEBHOOK_URL" ]; then
    exit 0
fi

# Get task info
TASK_NAME="${CLAUDE_TASK_NAME:-Unknown task}"
PROJECT_DIR="$(pwd)"
PROJECT_NAME="$(basename "$PROJECT_DIR")"

# Send Slack notification
curl -s -X POST "$WEBHOOK_URL" \
    -H "Content-Type: application/json" \
    -d "{
        \"text\": \"✅ Task completed in $PROJECT_NAME\",
        \"blocks\": [
            {
                \"type\": \"section\",
                \"text\": {
                    \"type\": \"mrkdwn\",
                    \"text\": \"*Task Completed*\n$TASK_NAME\"
                }
            },
            {
                \"type\": \"context\",
                \"elements\": [
                    {
                        \"type\": \"mrkdwn\",
                        \"text\": \"Project: $PROJECT_NAME\"
                    }
                ]
            }
        ]
    }" > /dev/null

exit 0

Cấu hình:

{
  "hooks": {
    "TaskCompleted": [
      {
        "hooks": ["~/.claude/hooks/notify-team.sh"]
      }
    ]
  }
}

6. Validate User Prompt

File: ~/.claude/hooks/validate-prompt.sh

#!/bin/bash
# Validate user prompts before processing

PROMPT="$CLAUDE_USER_PROMPT"

# Check for dangerous patterns
if echo "$PROMPT" | grep -iE "(delete all|rm -rf|drop database|format disk)" > /dev/null; then
    echo "⚠️  Warning: Potentially dangerous operation requested."
    echo "Please confirm you want to proceed with: $PROMPT"
    # Could integrate with approval system here
fi

# Check for prohibited actions in production
if [ "${ENVIRONMENT:-}" = "production" ]; then
    if echo "$PROMPT" | grep -iE "(deploy|migrate|seed)" > /dev/null; then
        echo "🚫 Production environment detected."
        echo "Direct deployment/migration commands are blocked."
        echo "Please use the proper CI/CD pipeline."
        exit 1
    fi
fi

exit 0

Environment Variables

Hooks nhận thông tin qua environment variables:

Tool Hooks

Variable Description
CLAUDE_TOOL_NAME Tên tool đang chạy
CLAUDE_TOOL_INPUT_PATH File path (cho file operations)
CLAUDE_TOOL_INPUT_COMMAND Command (cho bash)
CLAUDE_TOOL_OUTPUT Tool output (PostToolUse)

Session Hooks

Variable Description
CLAUDE_SESSION_ID Session ID
CLAUDE_SESSION_START Start timestamp
CLAUDE_PROJECT_DIR Project directory

Task Hooks

Variable Description
CLAUDE_TASK_NAME Task description
CLAUDE_TASK_ID Task ID
CLAUDE_USER_PROMPT User's prompt

Best Practices

1. Exit codes matter

# ✅ Good - Use exit codes properly
if [ -f "$FILE" ]; then
    do_something
    exit 0  # Success
else
    echo "File not found"
    exit 1  # Failure - may block operation
fi

2. Keep hooks fast

# ❌ Bad - Slow hook
npm install  # Don't do heavy operations

# ✅ Good - Quick check
if [ ! -d "node_modules" ]; then
    echo "⚠️  Dependencies not installed. Run: npm install"
fi

3. Fail gracefully

#!/bin/bash
# ✅ Good - Handle missing tools
if ! command -v prettier &> /dev/null; then
    # Prettier not installed, skip formatting
    exit 0
fi

prettier --write "$FILE_PATH"

4. Use logging

#!/bin/bash
LOG_FILE="${HOME}/.claude/logs/hooks.log"
mkdir -p "$(dirname "$LOG_FILE")"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}

log "Starting security scan for $FILE_PATH"
# ... hook logic
log "Security scan completed"

5. Conditional execution

#!/bin/bash
# ✅ Good - Only run for specific files
case "$FILE_PATH" in
    *.php|*.js|*.ts)
        # Run for code files
        run_security_scan
        ;;
    *.md|*.txt)
        # Skip for documentation
        exit 0
        ;;
esac

Debugging Hooks

Enable verbose logging

{
  "hooks": {
    "debug": true,
    "PreToolUse": [...]
  }
}

Test hook manually

# Simulate environment
export CLAUDE_TOOL_INPUT_PATH="/path/to/file.php"
export CLAUDE_TOOL_NAME="Write"

# Run hook
bash ~/.claude/hooks/format-code.sh

# Check exit code
echo "Exit code: $?"

Check logs

# View hook execution logs
tail -f ~/.claude/logs/hooks.log

# View Claude Code logs
tail -f ~/.claude/logs/claude-code.log

Cấu hình hoàn chỉnh

{
  "hooks": {
    "debug": false,
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          "~/.claude/hooks/format-code.sh",
          "~/.claude/hooks/backup-file.sh"
        ]
      },
      {
        "matcher": "Bash",
        "hooks": ["~/.claude/hooks/log-bash.sh"]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": ["~/.claude/hooks/security-scan.sh"]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": ["~/.claude/hooks/validate-prompt.sh"]
      }
    ],
    "SessionStart": [
      {
        "hooks": ["~/.claude/hooks/session-start.sh"]
      }
    ],
    "TaskCompleted": [
      {
        "hooks": ["~/.claude/hooks/notify-team.sh"]
      }
    ]
  }
}

Tổng kết

Hooks mang lại automation mạnh mẽ:

  • ✅ Event-driven execution
  • ✅ 25 hook events
  • ✅ Custom validation và formatting
  • ✅ Integration với external tools
  • ✅ Team notifications

Tiếp theo

Trong phần tiếp theo, chúng ta sẽ tìm hiểu MCP Protocol — cách kết nối Claude Code với external tools và APIs.

Tài liệu tham khảo


Series này được dịch và mở rộng từ claude-howto — MIT License.

Bình luận