03 / 06 · Продвинутый

🪝 Hooks — автоматизация

Hooks — скрипты Node.js (CJS), которые запускаются автоматически до/после действий Claude. Они перехватывают опасные команды, форматируют код, защищают критические файлы и считают стоимость сессии.

⚙️ Настройка
🗂️ Иерархия
🔌 MCP
🪝 Hooks
Финальный шаг настройки. Вы уже создали глобальный {CLAUDE_DIR}/settings.json, настроили иерархию и подключили MCP-серверы. Теперь добавляем Hooks — автоматические реакции на действия Claude.

📋 Обзор hooks системы

Новые возможности — май–июнь 2026 Новое · июнь 2026
  • Система теперь поддерживает 27 событий хуков (ранее — меньше)
  • Новый хук post-session (v2.1.169) — запускается после завершения сессии, для self-hosted runners
  • Новый хук CwdChanged — перезагрузка env при смене рабочей директории
  • Синтаксис Tool(param:value) (v2.1.178) — запрет/разрешение инструмента по значению параметра: Read(.env*), Edit(.github/**)
  • Исправлены glob-паттерны if-условий для путей Read/Edit/Write (v2.1.176)
  • 4 типа обработчиков: command, http, prompt, agent (до 50 tool-use turns)
Подробная документация с примерами →
HookСобытиеМатчерДействие
dangerous-command-guardPreToolUseBashБлокирует DROP TABLE, migrate:fresh и др.
bash-mcp-guardPreToolUseBashПеренаправляет docker → mcp__docker__*
secret-scannerPreToolUseBashБлокирует утечку API ключей
critical-files-guardPreToolUseEdit|Write|MultiEditЗащищает Caddyfile, .env.prod и др.
auto-formatPostToolUseEdit|Write|MultiEditАвто-форматирование pint/ruff/eslint
cost-trackerStop.*Логирует стоимость сессии
cwd-reload Новое CwdChanged.*Перезагружает env при смене директории

Как hooks регистрируются в settings.json

{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "node .claude/helpers/dangerous-command-guard.cjs" }, { "type": "command", "command": "node .claude/helpers/bash-mcp-guard.cjs" }, { "type": "command", "command": "node .claude/helpers/secret-scanner.cjs" } ] }, { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "node .claude/helpers/critical-files-guard.cjs" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ { "type": "command", "command": "node .claude/helpers/auto-format.cjs" } ] } ], "Stop": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "node .claude/helpers/cost-tracker.cjs" } ] } ] } }
💡
Exit codes: exit 0 — OK, продолжить. exit 1 — предупреждение (stderr), но продолжить. exit 2 — БЛОК, Claude отменяет действие и видит сообщение из stderr.
🛑 dangerous-command-guard.cjs
PreToolUse
Главный защитник. Блокирует команды, которые могут уничтожить данные или нарушить систему. Два уровня: HARD BLOCK (exit 2) и WARN (stderr, exit 0).
Матчер: Bash
Файл: .claude/helpers/dangerous-command-guard.cjs
// dangerous-command-guard.cjs 'use strict'; const input = JSON.parse(require('fs').readFileSync(0, 'utf8')); const cmd = (input.tool_input?.command || '').toLowerCase(); // УРОВЕНЬ 1: HARD BLOCK — exit 2, Claude видит ошибку const hardBlocked = [ // Laravel 'artisan migrate:fresh', 'artisan db:wipe', 'artisan migrate:reset', // SQL 'drop table', 'drop database', 'truncate table', 'delete from', 'delete *', // Git 'git push --force', 'git push -f', 'git reset --hard', 'git checkout --', 'git restore', '--no-verify', // БД напрямую 'psql ', 'pg_dump', 'mysqldump', // Docker volume 'docker volume rm', 'docker system prune', // rm -rf 'rm -rf', 'rm -r /', ]; for (const pattern of hardBlocked) { if (cmd.includes(pattern)) { process.stderr.write(`[BLOCKED] Опасная команда: "${pattern}"\n`); process.stderr.write(`Используйте MCP-инструменты или выполните вручную с CONFIRM_CRITICAL=yes\n`); process.exit(2); } } // УРОВЕНЬ 2: WARN — предупреждение, но продолжаем const warned = [ 'artisan migrate', 'npm ci', 'composer install --no-dev' ]; for (const pattern of warned) { if (cmd.includes(pattern)) { process.stderr.write(`[WARN] Потенциально опасная команда: "${pattern}". Убедитесь что это намеренно.\n`); } } process.exit(0);
🐳 bash-mcp-guard.cjs
PreToolUse
Перехватывает прямые docker-команды и подсказывает правильный MCP-инструмент. Это нужно потому что docker не в PATH на Windows и Bash hook блокирует прямой вызов.
// bash-mcp-guard.cjs 'use strict'; const input = JSON.parse(require('fs').readFileSync(0, 'utf8')); const cmd = (input.tool_input?.command || ''); const dockerMappings = [ { pattern: /^docker exec/, mcp: 'mcp__docker__docker_exec' }, { pattern: /^docker logs/, mcp: 'mcp__docker__docker_container_logs' }, { pattern: /^docker ps/, mcp: 'mcp__docker__docker_list_containers' }, { pattern: /^docker compose ps/, mcp: 'mcp__docker__docker_compose_ps' }, { pattern: /^docker compose up/, mcp: 'mcp__docker__docker_compose_up' }, { pattern: /^docker compose down/, mcp: 'mcp__docker__docker_compose_down' }, { pattern: /^docker inspect/, mcp: 'mcp__docker__docker_inspect_container' }, { pattern: /^docker stats/, mcp: 'mcp__docker__docker_container_stats' }, { pattern: /^docker restart/, mcp: 'mcp__docker__docker_restart_container' }, ]; for (const { pattern, mcp } of dockerMappings) { if (pattern.test(cmd.trim())) { process.stderr.write( `[DOCKER→MCP] Используй MCP инструмент: ${mcp}\n` + `Прямые docker команды заблокированы на Windows Server. MCP работает через Docker SDK.\n` ); process.exit(2); } } process.exit(0);
📁 critical-files-guard.cjs
PreToolUse
Защищает критически важные файлы от случайного изменения. При попытке редактирования — блок с объяснением. Обходится через переменную CONFIRM_CRITICAL=yes.
// critical-files-guard.cjs 'use strict'; const input = JSON.parse(require('fs').readFileSync(0, 'utf8')); const filePath = input.tool_input?.file_path || input.tool_input?.path || ''; if (process.env.CONFIRM_CRITICAL === 'yes') process.exit(0); const criticalPatterns = [ // Reverse proxy /Caddyfile/, /proxy[\/\\].*\.json/, // Системные /.wslconfig/, /docker-compose\.production/, // БД миграции (осторожно) /database[\/\\]migrations[\/\\]/, // Auth middleware /Middleware[\/\\]Authenticate/, // Env файлы /.env\.production/, /.env\.prod/, ]; for (const pattern of criticalPatterns) { if (pattern.test(filePath)) { process.stderr.write( `[PROTECTED] Файл "${filePath}" защищён от автоматического изменения.\n` + `Для редактирования: set CONFIRM_CRITICAL=yes (PowerShell: $env:CONFIRM_CRITICAL="yes")\n` ); process.exit(2); } } process.exit(0);
🔑 secret-scanner.cjs
PreToolUse
Сканирует bash-команды на наличие API ключей, токенов и других секретов. Самое важное — блокирует git add .env.
// secret-scanner.cjs 'use strict'; const input = JSON.parse(require('fs').readFileSync(0, 'utf8')); const cmd = input.tool_input?.command || ''; const secretPatterns = [ { pattern: /sk-[a-zA-Z0-9]{48}/, name: 'OpenAI API key' }, { pattern: /sk-ant-[a-zA-Z0-9]/, name: 'Anthropic API key' }, { pattern: /AKIA[0-9A-Z]{16}/, name: 'AWS Access Key' }, { pattern: /[0-9]{10}:[a-zA-Z0-9_-]{35}/, name: 'Telegram Bot Token' }, { pattern: /Bearer\s+[a-zA-Z0-9._-]{20,}/, name: 'Bearer Token' }, { pattern: /password\s*=\s*[^\s]{8,}/i, name: 'Password in command' }, { pattern: /git add .*\.env/, name: '.env файл в git!', hard: true }, { pattern: /git add -A/, name: 'git add -A (может включить .env)' }, ]; for (const { pattern, name, hard } of secretPatterns) { if (pattern.test(cmd)) { process.stderr.write(`[SECRET] Возможная утечка: ${name}\n`); process.stderr.write(`Команда: ${cmd.substring(0, 100)}\n`); if (hard) process.exit(2); } } process.exit(0);
✨ auto-format.cjs
PostToolUse
Автоматически форматирует файл после каждого изменения. PHP → Pint, Python → Ruff, TypeScript/Vue → ESLint. Никогда не блокирует (exit 0 всегда).
// auto-format.cjs 'use strict'; const { execSync } = require('child_process'); const input = JSON.parse(require('fs').readFileSync(0, 'utf8')); const filePath = input.tool_input?.file_path || input.tool_input?.path || ''; function run(cmd) { try { execSync(cmd, { stdio: 'pipe' }); } catch (e) { /* format errors не критичны */ } } if (filePath.endsWith('.php')) { // PSR-12 + declare(strict_types=1) run(`./vendor/bin/pint "${filePath}"`); } else if (filePath.endsWith('.py')) { // PEP 8 + imports sort run(`ruff check --fix "${filePath}"`); run(`ruff format "${filePath}"`); } else if (['.ts', '.vue', '.js'].some(ext => filePath.endsWith(ext))) { // ESLint flat config + Prettier run(`npx eslint --fix "${filePath}"`); } process.exit(0); // PostToolUse hooks НИКОГДА не должны блокировать
💰 cost-tracker.cjs
Stop
Запускается в конце каждой сессии. Рассчитывает стоимость по использованным токенам, пишет в JSONL файл. Алерт при превышении дневного бюджета.
// cost-tracker.cjs 'use strict'; const fs = require('fs'); const path = require('path'); const input = JSON.parse(fs.readFileSync(0, 'utf8')); const PRICES = { 'claude-opus-4-8': { input: 5, output: 25 }, // $ per 1M tokens 'claude-sonnet-5': { input: 3, output: 15 }, 'claude-haiku-4-5': { input: 1, output: 5 }, }; const model = input.model || 'claude-sonnet-5'; const usage = input.usage || {}; const price = PRICES[model] || PRICES['claude-sonnet-5']; const inputCost = (usage.input_tokens || 0) / 1_000_000 * price.input; const outputCost = (usage.output_tokens || 0) / 1_000_000 * price.output; const totalCost = inputCost + outputCost; const logDir = path.join('{CLAUDE_DIR}', 'logs'); const today = new Date().toISOString().slice(0, 7); const logFile = path.join(logDir, `cost-${today}.jsonl`); fs.mkdirSync(logDir, { recursive: true }); fs.appendFileSync(logFile, JSON.stringify({ timestamp: new Date().toISOString(), model, totalCost: totalCost.toFixed(4), inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }) + '\n'); const threshold = parseFloat(process.env.COST_ALERT_THRESHOLD || '5'); if (totalCost > threshold) { process.stderr.write(`[COST ALERT] Сессия: $${totalCost.toFixed(2)} (порог: $${threshold})\n`); } process.exit(0);

⚠️ Критические грабли Новое · июнь 2026

⚠️
Echo в .bashrc/.zshrc ломает JSON-парсер. Любой вывод в stdout из rc-файлов засоряет поток хука. Переносите echo в stderr: echo "..." >&2
⚠️
PostToolUse не отменяет действие. Хук запускается после выполнения инструмента — файл уже записан, команда выполнена. Для защиты используйте PreToolUse.
⚠️
Stop-хуки зацикливаются. Всегда проверяйте stop_hook_active из stdin и выходите если true: if (data.stop_hook_active) process.exit(0);
⚠️
Вывод обрезается после 10 000 символов. Claude видит только первые 10 000 символов вывода хука. Длинные логи пишите в файл, передавайте только путь.

Подробные примеры и объяснения: advanced/hooks.html → раздел «Критические грабли»

📥 Формат входных данных hook

Каждый hook получает JSON через stdin. Структура зависит от типа события:

// PreToolUse (Bash) — input.tool_input.command { "tool_name": "Bash", "tool_input": { "command": "php artisan migrate" } } // PreToolUse (Edit/Write) — input.tool_input.file_path { "tool_name": "Edit", "tool_input": { "file_path": "/path/to/file.php", "old_string": "...", "new_string": "..." } } // Stop — содержит usage статистику токенов { "model": "claude-sonnet-5", "usage": { "input_tokens": 45000, "output_tokens": 8000 } }

🎉 Конфиг полностью настроен!

Вы прошли все 4 шага. Итоговая структура вашего конфига:

# Глобальный уровень
{CLAUDE_DIR}/settings.json     # модель, permissions, hooks
{CLAUDE_DIR}/CLAUDE.md         # глобальные правила

# Проектный уровень
{PROJECT_DIR}/CLAUDE.md        # правила проекта
{PROJECT_DIR}/.mcp.json        # MCP-серверы проекта
{PROJECT_DIR}/.claude/         # хуки, команды, настройки
  hooks/                       # dangerous-command-guard.cjs и др.

Теперь изучите Workflow — как использовать этот конфиг в ежедневной работе.

Следующий шаг: Workflow →