Skip to content

Claude Code Hooks応用:プロダクション実装編

はじめに

Claude Code Hooks完全ガイドで基本概念を、AIエージェント自動化編で実践的な活用方法を解説しました。本記事では、実際のプロダクション環境で安定して動作させるための実装テクニックと、チーム開発での活用方法を詳しく解説します。

プロダクション対応Hook実装

1. エラーハンドリングとリトライ機構

#!/bin/bash
# production_hook.sh - エラーハンドリング付きHook

set -euo pipefail  # エラー時に即座に終了
IFS=$'\n\t'        # 安全なIFS設定

# ログ設定
LOG_DIR="$HOME/.claude-code/logs"
LOG_FILE="$LOG_DIR/hook_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$LOG_DIR"

# ログ関数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# エラーハンドラー
error_handler() {
    local line_no=$1
    local error_code=$2
    log "ERROR: Line $line_no exited with code $error_code"

    # Slackへの通知(オプション)
    if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
        curl -X POST "$SLACK_WEBHOOK_URL" \
            -H 'Content-Type: application/json' \
            -d "{\"text\":\"Hook Error: Line $line_no, Code $error_code\"}" \
            2>/dev/null || true
    fi

    exit $error_code
}

trap 'error_handler ${LINENO} $?' ERR

# リトライ機能
retry_with_backoff() {
    local max_attempts=3
    local timeout=1
    local attempt=1
    local exitCode=0

    while [ $attempt -le $max_attempts ]; do
        if "$@"; then
            return 0
        else
            exitCode=$?
        fi

        log "Attempt $attempt failed. Retrying in $timeout seconds..."
        sleep $timeout
        attempt=$(( attempt + 1 ))
        timeout=$(( timeout * 2 ))
    done

    log "Command failed after $max_attempts attempts"
    return $exitCode
}

# メイン処理
main() {
    local tool_name="${1:-unknown}"
    local file_path="${2:-}"

    log "Starting hook for tool: $tool_name, file: $file_path"

    # ファイルタイプに応じた処理
    case "$file_path" in
        *.py)
            log "Processing Python file"
            retry_with_backoff python -m py_compile "$file_path"
            retry_with_backoff ruff check "$file_path" --fix
            retry_with_backoff mypy "$file_path" --ignore-missing-imports
            ;;
        *.js|*.ts|*.jsx|*.tsx)
            log "Processing JavaScript/TypeScript file"
            retry_with_backoff npx eslint "$file_path" --fix
            retry_with_backoff npx prettier --write "$file_path"
            ;;
        *.md)
            log "Processing Markdown file"
            retry_with_backoff npx markdownlint "$file_path" --fix
            ;;
        *)
            log "No specific handler for file type"
            ;;
    esac

    log "Hook completed successfully"
}

# スクリプト実行
main "$@"

2. パフォーマンス最適化Hook

{
  "hooks": {
    "postToolUse": {
      "write": {
        "command": "~/.claude-code/hooks/optimized_hook.sh {{ tool_name }} {{ file_path }}"
      }
    }
  }
}
#!/bin/bash
# optimized_hook.sh - パフォーマンス最適化版

# 非同期処理キュー
QUEUE_DIR="$HOME/.claude-code/queue"
mkdir -p "$QUEUE_DIR"

# キューに追加(即座に返す)
queue_task() {
    local task_id=$(date +%s%N)
    local task_file="$QUEUE_DIR/$task_id.task"

    cat > "$task_file" << EOF
{
  "tool": "$1",
  "file": "$2",
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF

    # バックグラウンドでワーカー起動(既に起動していれば無視)
    if ! pgrep -f "claude_hook_worker" > /dev/null; then
        nohup ~/.claude-code/hooks/worker.sh > /dev/null 2>&1 &
    fi

    echo "Task queued: $task_id"
}

# メイン処理(即座に返す)
queue_task "$1" "$2"
exit 0
#!/bin/bash
# worker.sh - バックグラウンドワーカー

QUEUE_DIR="$HOME/.claude-code/queue"
PROCESSING_DIR="$HOME/.claude-code/processing"
mkdir -p "$PROCESSING_DIR"

# 既存のワーカーチェック
if [ -f "$PROCESSING_DIR/worker.pid" ]; then
    old_pid=$(cat "$PROCESSING_DIR/worker.pid")
    if kill -0 "$old_pid" 2>/dev/null; then
        echo "Worker already running (PID: $old_pid)"
        exit 0
    fi
fi

# PIDファイル作成
echo $$ > "$PROCESSING_DIR/worker.pid"

# クリーンアップ処理
cleanup() {
    rm -f "$PROCESSING_DIR/worker.pid"
    exit 0
}
trap cleanup EXIT INT TERM

# タスク処理
process_tasks() {
    while true; do
        # 最も古いタスクを取得
        task_file=$(ls -1t "$QUEUE_DIR"/*.task 2>/dev/null | tail -1)

        if [ -z "$task_file" ]; then
            # タスクがない場合は5秒待機
            sleep 5
            continue
        fi

        # タスクを処理中ディレクトリに移動
        processing_file="$PROCESSING_DIR/$(basename "$task_file")"
        mv "$task_file" "$processing_file" 2>/dev/null || continue

        # タスク実行
        if [ -f "$processing_file" ]; then
            # JSONパース(jqがインストールされている場合)
            if command -v jq &> /dev/null; then
                tool=$(jq -r '.tool' "$processing_file")
                file=$(jq -r '.file' "$processing_file")

                # 実際の処理を実行
                ~/.claude-code/hooks/process_file.sh "$tool" "$file"
            fi

            # 処理済みタスクを削除
            rm -f "$processing_file"
        fi
    done
}

# ワーカー開始
process_tasks

3. セキュリティ強化Hook

#!/bin/bash
# security_hook.sh - セキュリティチェック付きHook

# セキュリティ設定
readonly ALLOWED_DIRS=(
    "$HOME/projects"
    "$HOME/workspace"
    "/tmp/claude-code"
)

readonly FORBIDDEN_PATTERNS=(
    "password"
    "secret"
    "api_key"
    "private_key"
    "credentials"
)

# ディレクトリアクセス検証
validate_path() {
    local file_path="$1"
    local real_path=$(realpath "$file_path" 2>/dev/null)

    for allowed_dir in "${ALLOWED_DIRS[@]}"; do
        if [[ "$real_path" == "$allowed_dir"* ]]; then
            return 0
        fi
    done

    echo "ERROR: Access denied to path: $file_path" >&2
    exit 1
}

# センシティブ情報チェック
check_sensitive_data() {
    local file_path="$1"

    for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
        if grep -i "$pattern" "$file_path" > /dev/null 2>&1; then
            echo "WARNING: Possible sensitive data detected (pattern: $pattern)"

            # 確認プロンプト(インタラクティブな場合)
            if [ -t 0 ]; then
                read -p "Continue anyway? (y/N) " -n 1 -r
                echo
                if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                    exit 1
                fi
            fi
        fi
    done
}

# コマンドインジェクション対策
sanitize_input() {
    local input="$1"
    # 危険な文字をエスケープ
    echo "$input" | sed 's/[;&|`$]//g'
}

# メイン処理
main() {
    local tool_name=$(sanitize_input "${1:-}")
    local file_path=$(sanitize_input "${2:-}")

    # パス検証
    validate_path "$file_path"

    # センシティブデータチェック
    if [[ "$tool_name" == "write" ]] || [[ "$tool_name" == "edit" ]]; then
        check_sensitive_data "$file_path"
    fi

    # 実際の処理
    echo "Security checks passed for: $file_path"
}

main "$@"

チーム開発での活用

1. 共有Hook設定システム

#!/bin/bash
# setup_team_hooks.sh - チーム共有Hook設定

TEAM_REPO="https://github.com/your-team/claude-hooks.git"
HOOKS_DIR="$HOME/.claude-code/team-hooks"

# チームHookリポジトリをクローン/更新
setup_team_hooks() {
    if [ -d "$HOOKS_DIR/.git" ]; then
        echo "Updating team hooks..."
        cd "$HOOKS_DIR" && git pull
    else
        echo "Cloning team hooks..."
        git clone "$TEAM_REPO" "$HOOKS_DIR"
    fi

    # 実行権限付与
    chmod +x "$HOOKS_DIR"/*.sh

    # シンボリックリンク作成
    ln -sf "$HOOKS_DIR/hooks.json" "$HOME/.config/claude-code/hooks.json"
}

# 環境別設定
apply_environment_config() {
    local env="${CLAUDE_ENV:-development}"
    local env_config="$HOOKS_DIR/config/$env.json"

    if [ -f "$env_config" ]; then
        echo "Applying $env environment configuration..."
        jq -s '.[0] * .[1]' \
            "$HOOKS_DIR/hooks.json" \
            "$env_config" \
            > "$HOME/.config/claude-code/hooks.json"
    fi
}

# 実行
setup_team_hooks
apply_environment_config

echo "Team hooks setup completed!"

2. Hook実行ログの集約

#!/usr/bin/env python3
# aggregate_logs.py - Hookログ集約スクリプト

import json
import os
import glob
from datetime import datetime
from collections import defaultdict
import pandas as pd

class HookLogAggregator:
    def __init__(self, log_dir="~/.claude-code/logs"):
        self.log_dir = os.path.expanduser(log_dir)

    def parse_logs(self):
        """ログファイルを解析"""
        logs = []

        for log_file in glob.glob(f"{self.log_dir}/*.log"):
            with open(log_file, 'r') as f:
                for line in f:
                    if line.strip():
                        logs.append(self.parse_log_line(line, log_file))

        return logs

    def parse_log_line(self, line, filename):
        """ログ行をパース"""
        # [2025-01-18 12:34:56] Starting hook for tool: write, file: test.py
        parts = line.split(']', 1)
        if len(parts) == 2:
            timestamp_str = parts[0].strip('[')
            message = parts[1].strip()

            return {
                'timestamp': datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S'),
                'message': message,
                'filename': os.path.basename(filename),
                'level': self.detect_log_level(message)
            }
        return None

    def detect_log_level(self, message):
        """ログレベルを検出"""
        if 'ERROR' in message:
            return 'ERROR'
        elif 'WARNING' in message:
            return 'WARNING'
        elif 'INFO' in message:
            return 'INFO'
        return 'DEBUG'

    def generate_report(self):
        """レポート生成"""
        logs = [log for log in self.parse_logs() if log]

        if not logs:
            print("No logs found")
            return

        df = pd.DataFrame(logs)

        # エラー統計
        error_stats = df[df['level'] == 'ERROR'].groupby(
            df['timestamp'].dt.date
        ).size()

        # ツール使用統計
        tool_stats = defaultdict(int)
        for message in df['message']:
            if 'tool:' in message:
                tool = message.split('tool:')[1].split(',')[0].strip()
                tool_stats[tool] += 1

        # レポート出力
        print("=== Claude Code Hook Usage Report ===")
        print(f"\nTotal log entries: {len(df)}")
        print(f"Date range: {df['timestamp'].min()} - {df['timestamp'].max()}")

        print("\n--- Error Statistics ---")
        for date, count in error_stats.items():
            print(f"{date}: {count} errors")

        print("\n--- Tool Usage ---")
        for tool, count in sorted(tool_stats.items(), key=lambda x: x[1], reverse=True):
            print(f"{tool}: {count} times")

        # 詳細レポートをファイルに保存
        report_file = f"hook_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        with open(report_file, 'w') as f:
            json.dump({
                'summary': {
                    'total_entries': len(df),
                    'date_range': {
                        'start': str(df['timestamp'].min()),
                        'end': str(df['timestamp'].max())
                    },
                    'error_count': len(df[df['level'] == 'ERROR']),
                    'warning_count': len(df[df['level'] == 'WARNING'])
                },
                'tool_usage': dict(tool_stats),
                'daily_errors': {str(k): int(v) for k, v in error_stats.items()}
            }, f, indent=2)

        print(f"\nDetailed report saved to: {report_file}")

if __name__ == "__main__":
    aggregator = HookLogAggregator()
    aggregator.generate_report()

3. CI/CD統合

# .github/workflows/claude-hooks-test.yml
name: Claude Code Hooks Test

on:
  pull_request:
    paths:
      - '.claude-code/hooks/**'
      - 'hooks.json'

jobs:
  test-hooks:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'

    - name: Install dependencies
      run: |
        npm install -g eslint prettier markdownlint-cli
        pip install ruff mypy

    - name: Test Hook Scripts
      run: |
        # Hookスクリプトの構文チェック
        for hook in .claude-code/hooks/*.sh; do
          echo "Testing $hook..."
          bash -n "$hook"
        done

    - name: Test Hook Configuration
      run: |
        # hooks.jsonの検証
        python -m json.tool hooks.json > /dev/null
        echo "Hook configuration is valid JSON"

    - name: Simulate Hook Execution
      run: |
        # テストファイルでHook実行をシミュレート
        echo "print('test')" > test.py
        .claude-code/hooks/production_hook.sh write test.py || true

    - name: Check Hook Logs
      run: |
        if [ -d "$HOME/.claude-code/logs" ]; then
          echo "Hook logs:"
          cat $HOME/.claude-code/logs/*.log || true
        fi

トラブルシューティングガイド

問題1: Hookが実行されない

#!/bin/bash
# debug_hooks.sh - Hook診断スクリプト

echo "=== Claude Code Hooks Diagnostic ==="

# 1. 環境変数チェック
echo -e "\n1. Environment Variables:"
echo "CLAUDE_CODE_HOOKS_ENABLED: ${CLAUDE_CODE_HOOKS_ENABLED:-not set}"
echo "CLAUDE_CODE_HOOKS_CONFIG: ${CLAUDE_CODE_HOOKS_CONFIG:-not set}"

# 2. 設定ファイルチェック
echo -e "\n2. Configuration Files:"
config_file="${CLAUDE_CODE_HOOKS_CONFIG:-$HOME/.config/claude-code/hooks.json}"
if [ -f "$config_file" ]; then
    echo "Config file exists: $config_file"
    echo "Config file valid: $(python -m json.tool "$config_file" > /dev/null 2>&1 && echo "Yes" || echo "No")"
else
    echo "Config file NOT FOUND: $config_file"
fi

# 3. Hookスクリプト実行権限
echo -e "\n3. Hook Scripts Permissions:"
hook_dir="$HOME/.claude-code/hooks"
if [ -d "$hook_dir" ]; then
    for script in "$hook_dir"/*.sh; do
        if [ -f "$script" ]; then
            echo "$(basename "$script"): $([ -x "$script" ] && echo "executable" || echo "NOT executable")"
        fi
    done
else
    echo "Hook directory NOT FOUND: $hook_dir"
fi

# 4. 依存関係チェック
echo -e "\n4. Dependencies:"
commands=("bash" "jq" "curl" "git" "npm" "python3")
for cmd in "${commands[@]}"; do
    echo "$cmd: $(command -v $cmd > /dev/null && echo "installed" || echo "NOT installed")"
done

# 5. テスト実行
echo -e "\n5. Test Hook Execution:"
test_hook="$hook_dir/test_hook.sh"
cat > "$test_hook" << 'EOF'
#!/bin/bash
echo "Test hook executed successfully!"
exit 0
EOF
chmod +x "$test_hook"

if $test_hook > /dev/null 2>&1; then
    echo "Test hook execution: SUCCESS"
else
    echo "Test hook execution: FAILED"
fi

rm -f "$test_hook"

問題2: パフォーマンスの問題

# performance_monitor.sh - パフォーマンスモニタリング

#!/bin/bash
METRICS_FILE="$HOME/.claude-code/metrics.log"

# Hook実行時間を計測
measure_hook_performance() {
    local hook_name="$1"
    local start_time=$(date +%s.%N)

    # Hook実行
    shift
    "$@"
    local exit_code=$?

    local end_time=$(date +%s.%N)
    local duration=$(echo "$end_time - $start_time" | bc)

    # メトリクス記録
    echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),${hook_name},${duration},${exit_code}" >> "$METRICS_FILE"

    # 遅い実行を警告
    if (( $(echo "$duration > 5" | bc -l) )); then
        echo "WARNING: Hook '$hook_name' took ${duration}s to execute"
    fi

    return $exit_code
}

# 使用例
measure_hook_performance "code_quality_check" ~/.claude-code/hooks/lint.sh "$@"

まとめ

Claude Code Hooksをプロダクション環境で活用するには、エラーハンドリング、パフォーマンス最適化、セキュリティ対策が不可欠です。本記事で紹介した実装パターンを参考に、チームの要求に合わせてカスタマイズしてください。

実装チェックリスト

  • エラーハンドリングとログ記録の実装
  • 非同期処理によるパフォーマンス最適化
  • セキュリティチェックの導入
  • チーム共有設定の構築
  • CI/CDパイプラインへの統合
  • モニタリングとアラートの設定

次回は、Claude Code Hooksを使った高度なワークフロー自動化について解説します。

関連記事


この記事は2025年1月18日に公開されました。実装例は継続的に更新されています。