Claude Code Hooks — автоматизация, о которой ты не знал

Hooks в Claude Code позволяют запускать свои команды до и после любого действия Claude. Линтер, форматирование, блокировка файлов — автоматически.

Claude Code Hooks — автоматизация, о которой ты не знал
TL;DR: Hooks в Claude Code — это пользовательские команды, которые автоматически запускаются до или после определённых событий: редактирование файла, запуск bash, начало сессии. Можно настроить авто-линтинг, защиту конфигов, персистентное окружение.

Claude Code пишет код, запускает команды, редактирует файлы. Но иногда хочется, чтобы после каждого редактирования Python-файла автоматически запускался ruff format, или чтобы Claude не мог случайно перезаписать docker-compose.prod.yml. Hooks решают именно это.

Как работают hooks

Hook — это команда, которая привязана к событию жизненного цикла Claude Code. Событие наступает — команда запускается. Всё просто.

Есть несколько типов событий:

  • SessionStart — при старте сессии Claude Code
  • PreToolUse — перед выполнением инструмента (Bash, Edit, Write)
  • PostToolUse — после выполнения инструмента
  • Notification — когда Claude отправляет уведомление

Настройка через settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "ruff format \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

Этот пример запускает ruff format после каждого редактирования файла. matcher фильтрует, на какие инструменты реагировать — здесь на Edit и Write.

Персистентное окружение через SessionStart

Одна из главных проблем Claude Code: переменные окружения не сохраняются между bash-командами. Каждая команда запускается в чистом шелле. Если ты работаешь с conda или virtualenv — это боль.

Решение через SessionStart hook:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'source /path/to/venv/bin/activate' >> \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

$CLAUDE_ENV_FILE — специальный файл, который Claude Code сорсит перед каждой bash-командой. Всё, что ты туда запишешь при старте, будет доступно в каждой последующей команде.

Это решает проблему, которая мучала многих: нужно активировать conda environment, но Claude Code забывает об этом после первой команды.

Защита файлов от записи

Допустим, у тебя есть продакшн-конфиги, которые Claude точно не должен трогать. Можно заблокировать через deny-правила в permissions, а можно через PreToolUse hook:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$CLAUDE_FILE_PATH\" | grep -q 'deploy/prod' && echo 'BLOCK: production configs are read-only' && exit 1 || exit 0"
          }
        ]
      }
    ]
  }
}

Если hook возвращает ненулевой код выхода, действие Claude блокируется. Здесь — любая попытка отредактировать файл в deploy/prod/ будет остановлена.

Мне кажется, это надёжнее, чем deny-правила в permissions, потому что hook может проверять более сложные условия — имя ветки, время суток, что угодно.

Авто-форматирование после редактирования

Самый популярный use case. Claude пишет код, который работает, но форматирование бывает слегка кривое. Hook решает:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "ext=\"${CLAUDE_FILE_PATH##*.}\"; case \"$ext\" in py) ruff format \"$CLAUDE_FILE_PATH\" 2>/dev/null;; js|ts|tsx) npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null;; esac; true"
          }
        ]
      }
    ]
  }
}

Определяет расширение файла и запускает нужный форматер: ruff для Python, prettier для JavaScript/TypeScript. Финальный true гарантирует, что hook не заблокирует работу, даже если форматер упал.

Переменные окружения в hooks

Hooks получают набор переменных окружения с контекстом:

  • CLAUDE_PROJECT_DIR — корневая директория проекта
  • CLAUDE_FILE_PATH — путь к файлу (для Edit/Write/Read)
  • CLAUDE_ENV_FILE — путь к файлу окружения (для SessionStart)
  • CLAUDE_TOOL_NAME — имя инструмента (Bash, Edit, Write)

Эти переменные позволяют писать умные hooks, которые адаптируются к контексту.

Где хранить hooks

Hooks поддерживают ту же систему скоупов, что и settings:

  • ~/.claude/settings.json — глобальные hooks для всех проектов
  • .claude/settings.json — проектные hooks, общие для команды
  • .claude/settings.local.json — локальные, только для тебя

Для команды удобно хранить hooks в проектном settings.json — тогда у всех разработчиков будет одинаковое поведение. Личные hooks (вроде активации conda) лучше в пользовательский файл.

Есть ещё allowManagedHooksOnly в managed settings — для организаций, которые хотят контролировать, какие hooks разрешены. При включении работают только hooks из managed settings и SDK.

Часто задаваемые вопросы

Замедляют ли hooks работу Claude Code? Зависит от самого hook. Быстрые команды вроде ruff format добавляют миллисекунды. Но если hook делает что-то тяжёлое (полный билд, запуск тестов), задержка будет заметна. Оптимизируй команды.

Можно ли отключить hooks временно? Да. Параметр disableAllHooks: true в settings.json отключает все hooks разом.

Что будет, если hook упадёт с ошибкой? Зависит от типа. PreToolUse hook с ненулевым exit code блокирует действие. PostToolUse hook с ошибкой — нет, действие уже выполнено. Рекомендую добавлять || true к PostToolUse hooks.

Что ещё почитать