Автоматическая проверка кода с Hooks: качество гарантировано при написании

PostToolUse Hooks автоматически запускают lint после каждой записи файла. Код выхода 2 возвращает ошибки Claude для автоматического исправления.


Claude Code пишет код быстро, но не всегда помнит запустить lint. Вы просите его "проверить заодно" — иногда делает, иногда нет. Hooks решают эту проблему: переносят ответственность за проверку кода с Claude на систему.

Эта статья сосредоточена на одном: использовать PostToolUse Hooks для автоматического запуска проверок после написания кода Claude, чтобы каждое изменение файла соответствовало вашим стандартам.


Подход: PostToolUse + фильтрация по типу файла

Логика Hook проверки кода проста:

  1. Claude вызывает Write или Edit для записи файла
  2. Hook перехватывает событие и получает путь к файлу
  3. По расширению определяет, какой инструмент запустить
  4. Проверка прошла: код выхода 0, Claude продолжает
  5. Проверка не прошла: код выхода 2, ошибки передаются Claude для исправления

Код выхода 2 — ключевой: Claude получает ошибку и автоматически исправляет её, формируя цикл "написать→проверить→исправить".


Базовая настройка: проекты на одном языке

Ruby (RuboCop)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
          }
        ]
      }
    ]
  }
}

JavaScript/TypeScript (ESLint)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" =~ \\.(js|ts|jsx|tsx)$ ]] && npx eslint --fix \"$file\" || true'"
          }
        ]
      }
    ]
  }
}

Python (ruff)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.py ]] && ruff check --fix \"$file\" && ruff format \"$file\" || true'"
          }
        ]
      }
    ]
  }
}

Расширенная настройка: многоязычные проекты

Перенос логики проверки в отдельный скрипт гораздо удобнее для сопровождения.

Создайте .claude/hooks/lint.sh:

#!/bin/bash
set -e

input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // empty')

[[ -z "$file" ]] && exit 0
[[ ! -f "$file" ]] && exit 0

ext="${file##*.}"

case "$ext" in
  rb)    rubocop -A "$file" --no-color -q ;;
  js|ts|jsx|tsx) npx eslint --fix "$file" --quiet ;;
  py)    ruff check --fix "$file"; ruff format "$file" ;;
  go)    gofmt -w "$file"; golangci-lint run "$file" 2>&1 ;;
  *)     exit 0 ;;
esac

Укажите скрипт в settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "bash .claude/hooks/lint.sh" }]
      }
    ]
  }
}

Автоматическое исправление через Claude

Код выхода 2 + stderr запускает автоматическое исправление:

run_lint() {
  local output exit_code
  output=$(rubocop "$file" --no-color 2>&1)
  exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    echo "$output" >&2
    exit 2
  fi
  rubocop -A "$file" --no-color -q
}

Исключение файлов, не требующих проверки

skip_patterns=("vendor/" "node_modules/" "db/schema.rb" ".min.js" "_test.go")
for pattern in "${skip_patterns[@]}"; do
  [[ "$file" == *"$pattern"* ]] && exit 0
done

На практике

После настройки рабочий процесс выглядит так:

  1. Просите Claude написать Ruby-файл
  2. Claude записывает его инструментом Write
  3. Hook автоматически запускает rubocop -A — находит 3 проблемы
  4. RuboCop автоматически исправляет 2; третья требует человеческого суждения
  5. Третья ошибка отправляется Claude через stderr
  6. Claude исправляет код, Hook срабатывает снова
  7. Вторая проверка пройдена — готово

Вам не нужно вручную запускать никаких команд проверки.


Глобальный vs. уровень проекта

Глобальный (~/.claude/settings.json): Когда используете одни правила во всех проектах.

Уровень проекта (.claude/settings.json в git): Рекомендуется для команд. Любой, кто открывает репозиторий, автоматически получает правильную конфигурацию.

Оба уровня сосуществуют; hooks проекта объединяются с глобальными.


Итог

Автоматическая проверка кода с Hooks сводится к трём шагам:

  1. PostToolUse + matcher "Write|Edit" для перехвата записи файлов
  2. Получить путь к файлу и направить к нужному linter по расширению
  3. При ошибке выйти с кодом 2, чтобы Claude увидел ошибку и исправил

Начните с одного языка. Когда заработает — добавьте остальные. Зафиксируйте скрипты в .claude/hooks/ для общего доступа команды.