# Хуки в Claude Code: 7 примеров для settings.json

> Практическая инструкция по хукам Claude Code: настройка через .claude/settings.json, 4 типа обработчиков, 31 событие и 7 примеров для защиты .env, форматирования, Git и контекста сессии.

Source: https://ailoot.ai/blog/ru/claude-code-hooks/
Canonical: https://ailoot.ai/blog/ru/claude-code-hooks/
Language: ru
Publisher: Ailoot (https://ailoot.ai/)
Translation: guide-121
Revision: sha256-93a6b170169495cd112d226f6bc345108c63af2f3abc6c80887bd1f48b80279d


Хуки Claude Code — это shell-команды, HTTP-запросы или короткие проверки с помощью LLM, которые автоматически запускаются до или после действия Claude. Они помогают обеспечить правила, на которые нельзя полагаться в одном лишь CLAUDE.md: например, запретить удаление `.env`, блокировать force-push в main, форматировать изменённый код или добавлять актуальный контекст при старте сессии. В статье разобраны 4 типа обработчиков, 31 событие жизненного цикла, способ определить, какое правило стоит вынести в hook, 7 готовых примеров для `.claude/settings.json` и 3 частые проблемы.

[Документация Ailoot: API, ключи и подключение](https://ailoot.ai/docs/).

## Что такое хуки в Claude Code?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Хук — это shell-команда, HTTP-запрос, короткий LLM-промпт или субагент, запускаемый Claude Code при определённом событии жизненного цикла. В отличие от текста в `CLAUDE.md`, который модель может упустить из внимания, хук при наступлении события выполняется по заданным правилам: это код, а не просьба.

</div>

Правило «не редактировать `.env` напрямую» долго оставалось у меня в `CLAUDE.md`, но в продолжительных сессиях Claude иногда его забывал. Однажды в файл попал лишний токен, а в другой раз переменная с повторяющимся именем перезаписала рабочую.

При совпавшем прямом Edit с путём .env hook отклоняет этот вызов. Перенаправление Bash, скрипты, MCP и другие пути требуют отдельных ограничений. Файл не становится неизменяемым.

<figure>
<blockquote>
<p>Хуки — это заданные вами shell-команды, HTTP-endpoints или LLM-prompts, которые автоматически запускаются в определённых точках жизненного цикла Claude Code.</p>
</blockquote>
<figcaption>— Anthropic, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

Проще говоря, хук — ваша команда, которую Claude Code запускает при заданном событии. Таким событием может быть редактирование файла, запуск Bash, начало сессии или завершение субагента.

Хуки добавляют проверки отдельных событий, форматирование после правки и свежий контекст сессии. Совпадающий PreToolUse может отклонить вызов; regex-примеры — удобные ограничения конкретных путей, не полная защита доступа к файлам и Git. Примеры:

- Защищать файлы с секретами от случайных изменений.
- Запускать prettier или eslint после генерации кода.
- Запрещать `git push --force` в main без явного разрешения.
- Добавлять свежий контекст при старте каждой сессии.
- Записывать действия Claude для последующего аудита.

`CLAUDE.md` задаёт контекст, а хуки управляют **действиями**. Это разные механизмы, и смешивать их назначение — распространённая ошибка.

## Чем хуки отличаются от CLAUDE.md и скиллов?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

`CLAUDE.md` содержит текст, который модель читает в начале сессии и старается учитывать. Хук — команда, запускаемая процессом Claude Code при событии, независимо от модели. Скилл — набор инструкций с именем, который Claude может вызвать через инструмент Skill. У каждого из этих механизмов своя задача.

</div>

Anthropic отдельно объясняет различие между `CLAUDE.md` и хуками:

<figure>
<blockquote>
<p>Содержимое CLAUDE.md поступает как user message после system prompt, а не включается в system prompt. Claude читает его и старается следовать указаниям, однако строгой гарантии соблюдения нет.</p>
</blockquote>
<figcaption>— Anthropic, документация по памяти, https://code.claude.com/docs/en/memory</figcaption>
</figure>

Иными словами, содержимое `CLAUDE.md` приходит Claude как начальное сообщение пользователя, а не как системная инструкция. Модель прочитает его и постарается учесть, но гарантировать выполнение нельзя. В длинной сессии контекст увеличивается, и ранние инструкции могут потеряться.

Hook запускается процессом Claude Code по событию. PreToolUse может проверить и отклонить ещё не выполненный вызов, PostToolUse обрабатывает результат уже выполненного действия. Не приравнивайте обратную связь после записи к запрету самой записи.

| Признак | CLAUDE.md | Hook | Skill |
|----|----|----|----|
| Где хранится | `CLAUDE.md` в корне проекта | `.claude/settings.json` | Папка `.claude/skills/` |
| Кто запускает | Модель читает как часть user message | Процесс Claude Code при событии | Модель через инструмент Skill |
| Насколько гарантирован запуск | Нет, выполнение зависит от модели | Совпавший настроенный command-hook выполняет код; отказ и exit-поведение зависят от события | Вызывается моделью; грант есть, но на уровне shell гарантий нет |
| Для чего подходит | Контекст проекта, тон, стиль, маршрутизация | Запреты, форматирование и проверки отдельных файлов | Сложные многоэтапные процедуры с параметрами |
| Когда срабатывает | При каждом сообщении модели в сессии | При событии жизненного цикла | По вызову модели |
| Может ли заблокировать действие | Нет | Совпавший PreToolUse с exit2; PostToolUse только сообщает после выполнения | Нет |

Шаблон `CLAUDE.md` и правила его поддержки приведены в гайде [Как настроить CLAUDE.md в 2026: готовый шаблон и 6 правил](https://ailoot.ai/blog/ru/kak-nastroit-claude-md/). О создании именованных навыков читайте в статье [Claude Code Skills: как собрать библиотеку под себя](https://ailoot.ai/blog/ru/claude-code-skills-library/).

Хук работает на нижнем уровне: это обычная shell-команда, которой не требуется интерпретация модели.

Хуки — один из инструментов [контекст-инжиниринга](https://ailoot.ai/blog/ru/context-engineering/), наряду с Plan Mode и /compact.

## 4 типа хуков: Command, HTTP, Prompt, Agent — какой выбрать?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

В Claude Code доступны 4 типа обработчиков. Command запускает shell-скрипт и подходит для большинства проверок. HTTP отправляет POST-запрос на заданный URL — например, для командных интеграций. Prompt передаёт короткий запрос модели Haiku для семантической оценки. Agent запускает субагента с доступом к инструментам для более глубокой проверки. Для начала обычно достаточно Command и Prompt.

</div>

| Тип | Что делает | Когда подходит | Сложность |
|----|----|----|----|
| **Command** | Передаёт JSON через stdin shell-команде; результат задаётся exit code и stdout | Проверка путей, форматирование, поиск по содержимому, lsof и операции Git | низкая |
| **HTTP** | Отправляет JSON в теле POST-запроса на заданный URL и получает JSON-ответ | Для командных процессов и интеграций: общий валидатор, панель статусов или удалённый аудит | средняя (нужен сервер) |
| **Prompt** | Отправляет короткий промпт в Haiku (по умолчанию) и получает ответ для семантической оценки | Когда поиска по текстовому шаблону недостаточно, например для оценки корпоративного тона | низкая |
| **Agent** | Запускает субагента с доступом к Read/Grep/Glob для проверки в несколько шагов | Для сложных задач, например проверки соответствия изменённых файлов тестам | высокая |

Частая ошибка — пытаться решить с помощью Command задачу, требующую семантической оценки. Правило «не использовать длинное тире в `.md`» можно проверить через grep, поэтому здесь подходит Command. А оценка «не писать корпоративным тоном» требует понимания смысла и лучше подходит для Prompt.

Для начала попробуйте Command: все 7 примеров ниже относятся к этому типу. HTTP, Prompt и Agent можно подключить позже, когда возникнет подходящая задача.

## Где находится settings.json и какие есть 4 уровня конфигурации?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Claude Code использует конфигурации четырёх уровней: корпоративную, распространяемую через MDM; пользовательскую (`~/.claude/settings.json`); проектную (`.claude/settings.json` в корне репозитория); локальную (`.claude/settings.local.json`, не включаемую в Git). При запуске сессии файлы объединяются, а при конфликте приоритет получает более низкий уровень.

</div>

| Уровень | Путь | Где применяется | В git? |
|----|----|----|----|
| Корпоративный | `/Library/Application Support/ClaudeCode/settings.json` (Mac), `/etc/claude-code/settings.json` (Linux) | Для команды через MDM | нет |
| Пользовательский | `~/.claude/settings.json` | Во всех проектах пользователя | нет |
| Проектный | `.claude/settings.json` в корне репозитория | В текущем проекте для всей команды | **да** |
| Локальный | `.claude/settings.local.json` | Только в этом проекте и для этого пользователя | **нет** (в `.gitignore`) |

Я размещаю все 7 описанных ниже хуков в **проектном** `.claude/settings.json`. Тогда при клонировании репозитория настройки доступны команде, и Claude применяет общие правила. Личные эксперименты храню в `.claude/settings.local.json`.

Все четыре файла объединяются при запуске сессии. Если настройки конфликтуют, приоритет у более низкого уровня: локальный важнее проектного, а проектный — пользовательского.

**Самый быстрый способ добавить первый хук** — открыть в Claude Code меню `/hooks`. Выберите событие и matcher, например `Bash` или `Edit`, затем задайте команду. Claude Code сохранит корректную структуру JSON в `settings.json`.

## 31 событие жизненного цикла: что пригодится начинающим?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

В Claude Code указано 31 событие жизненного цикла. Они происходят с разной частотой: один раз за сессию (`SessionStart`/`SessionEnd`), один раз за ход (`UserPromptSubmit`/`Stop`) или при вызове инструмента (`PreToolUse`/`PostToolUse`). Для начала обратите внимание на четыре: `PreToolUse` для запретов, `PostToolUse` для изменений после действия, `SessionStart` для загрузки контекста и `Stop` для финальной проверки.

</div>

<figure>
<blockquote>
<p>События возникают с тремя частотами: один раз за сессию (SessionStart, SessionEnd), один раз за turn (UserPromptSubmit, Stop, StopFailure) и при каждом tool call внутри agentic loop (PreToolUse, PostToolUse).</p>
</blockquote>
<figcaption>— Anthropic, справочник по хукам, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

Чтобы выбрать подходящее событие, полезно различать три частоты срабатывания:

1.  **Один раз за сессию** — `SessionStart`, `SessionEnd`, `Setup`. Эти события возникают при открытии или закрытии Claude Code и подходят для инициализации контекста или очистки временных файлов.
2.  **Один раз за ход** — `UserPromptSubmit`, `UserPromptExpansion`, `Stop`, `StopFailure`. Они срабатывают при отправке сообщения и остановке модели; их можно использовать для записи диалога и итоговой проверки результата.
3.  **При каждом вызове инструмента** — `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`, `PostToolBatch`, `SubagentStart`, `SubagentStop`. Такие события возникают при действиях вроде редактирования файла, запуска Bash, чтения и поиска. Здесь размещают запреты, форматирование и проверки.

Среди 31 события есть также асинхронные и реактивные: `TeammateIdle`, `PreCompact`, `PostCompact`, `Elicitation`, `ElicitationResult`, `InstructionsLoaded`, `ConfigChange`, `CwdChanged`, `FileChanged`, `WorktreeCreate`, `WorktreeRemove`, `Notification`. Они нужны для более специализированных сценариев.

## Проверка из 5 вопросов: когда правило переносить в hook, а когда оставить в CLAUDE.md?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Переносите правило в хук, если оно относится к действиям, касается безопасности, денег или данных, поддаётся проверке через grep или regex, а нарушение сложно отменить либо Claude уже забывал это правило. Если утвердительно ответили менее чем на три из пяти вопросов, оставьте его в `CLAUDE.md`. При разборе своего файла на 80 строк автор перенёс в хуки 5 правил.

</div>

Я использую пять вопросов, чтобы решить, где хранить правило — в `CLAUDE.md` или в хуке. Если набирается не менее трёх ответов «да», правило подходит для хука. При меньшем числе ответов его можно оставить в текстовом файле.

**Проверьте, стоит ли перенести правило в hook:**

1.  Оно задаёт действие, а не описывает контекст? («не запускай `rm -rf`» — действие; «проект написан на TypeScript» — контекст.)
2.  Оно связано с безопасностью, деньгами или данными? Например, «не меняй `.env`» или «не публикуй ключи».
3.  Его можно проверить через grep, regex или exit code shell-команды? Например, проверить длинное тире после редактирования `.md`.
4.  Нарушение будет сложно исправить? Например, force push в main отменить сложнее, чем пропущенную точку в конце предложения.
5.  Вы уже замечали, что Claude забывает это правило?

**Пример: «не используй длинное тире, пиши обычный дефис».** Это правило о действии — да. О безопасности — нет. Проверяется через grep — да. Последствия сложно отменить — нет. Нарушение уже встречалось — да. **3 ответа из 5: правило подходит для хука.**

**Контрпример: «пиши без сленга для пользователей, которые не занимаются разработкой».** Это скорее контекст, чем действие. К безопасности не относится. Надёжно проверить regex сложно. Нарушение легко исправить. Даже если оно уже случалось, получается **1 ответ из 5 — правило остаётся в `CLAUDE.md`.**

В описанном примере проверку прошли 5 правил, которые автор перенёс в хуки. Остальные 75% остались в текстовом файле.

## 7 готовых хуков для копирования

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Ниже приведены 7 примеров для `.claude/settings.json`: запрет на изменение `.env`, запуск prettier, блокировка force-push в main, проверка длинного тире, освобождение порта перед `npm run dev`, автоматический коммит после успешных тестов и добавление свежих сведений при старте сессии. Вставьте выбранный пример в файл настроек и перезапустите Claude Code.

</div>

Все семь хуков размещаются в `.claude/settings.json` в корне проекта. Каждый содержит событие, фильтр инструмента (matcher) и shell-команду. Exit2 отклоняет совпавший PreToolUse до исполнения. В PostToolUse действие уже произошло: hook сообщает обратную связь, не отменяя его.

<figure>
<blockquote>
<p>Для большинства событий блокировку вызывает только exit code 2. Claude Code считает exit code 1 неблокирующей ошибкой и продолжает действие, хотя в Unix-контексте 1 обычно означает ошибку.</p>
</blockquote>
<figcaption>— Anthropic, справочник по хукам, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

`exit 0` означает успешное выполнение. `exit 1` сообщает об ошибке, но не останавливает Claude — это первая ловушка, описанная ниже. `exit 2` зависит от события: PreToolUse может блокироваться, а PostToolUse сообщает о уже выполненном действии.

### Хук1. Проверка прямой правки .env (PreToolUse, exit2)

Этот хук блокирует прямое редактирование файлов `.env`, `.env.local`, `.env.production` и других имён, соответствующих шаблону.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Запрещено редактировать .env напрямую. Скажи мне какую переменную добавить, я сам открою файл.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}
```

**Как работает:** перед `Edit`, `Write` или `MultiEdit` команда получает путь, проверяет его через grep и при совпадении с шаблоном `.env` возвращает exit 2 с пояснением. Claude получает stderr и сообщает, что попытка изменения заблокирована.

### Хук 2. Запуск prettier после редактирования кода (PostToolUse, exit 0)

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
          }
        ]
      }
    ]
  }
}
```

**Как работает:** после `Edit` или `Write` команда проверяет расширение файла. Для `.ts`, `.tsx`, `.js`, `.jsx` и `.json` она запускает prettier в фоне через `&`. Хук всегда возвращает exit 0: форматирование не должно блокировать действие. Если prettier завершится с ошибкой, исходный файл останется без этой дополнительной обработки.

Хук 1 срабатывает на `PreToolUse` и exit 2, поэтому блокирует действие до его выполнения. Хук 2 запускается на `PostToolUse` и возвращает exit 0, чтобы обработать результат после редактирования. Эти режимы предназначены для разных задач.

### Хук 3. Запрет `git push --force` в main (PreToolUse, exit 2)

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push в main запрещён. Сделай новую ветку или попроси меня вручную.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}
```

**Как работает:** перед запуском Bash проверяется, содержит ли команда `push`, флаг `--force`, `--force-with-lease` или `-f`, а также указание на `main`. Если совпали все три условия, хук останавливает действие и сообщает причину.

В исходном примере автор хранил это правило в `CLAUDE.md`, но Claude иногда его нарушал при просьбе выполнить force push для отката неудачного коммита. Hook ловит показанные буквальные команды. Алиасы, скрипты, неявная ветка назначения и другой Git-клиент обходят строковую проверку. Защитите main на сервере и ограничьте ключи агента.

### Хук 4. Поиск длинного тире в текстовых файлах (PostToolUse, exit 2)

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"В файле $FILE найдено длинное тире (U+2014). Замени на обычный дефис (U+002D).\" >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}
```

**Как работает:** PostToolUse ищет длинное тире после уже выполненной правки. Exit2 сообщает Claude об ошибке последующей проверки, но не отменяет и не предотвращает запись. Затем можно исправить символ.

Команда ищет именно символ U+2014 через bash-запись `$'\xe2\x80\x94'`, задающую его байты в UTF-8. Если искать обычный дефис с помощью `grep -q '-'`, проверка будет срабатывать на каждом файле, где есть дефис.

### Хук 5. Освобождение порта 3000 перед `npm run dev` (PreToolUse, exit 0)

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Порт 3000 освобождён перед запуском' >&2; }; exit 0"
          }
        ]
      }
    ]
  }
}
```

**Как работает:** перед командой `npm run dev`, `pnpm run dev` или `yarn run dev` хук завершает процесс, занимающий порт 3000. Он всегда возвращает exit 0, поскольку подготавливает среду, а не запрещает действие. Это помогает избежать ситуации, когда запущенный ранее сервер мешает новому запуску.

### Хук 6. Автоматический коммит после успешных тестов (PostToolUse, exit 0)

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
          }
        ]
      }
    ]
  }
}
```

**Как работает:** читает JSON stdin один раз, принимает только показанные точные команды тестов и продолжает лишь при явном tool_response.exit_code=0. При отсутствии поля в вашей версии пропускает коммит, не угадывая успех. Замените src/orders.ts и tests/orders.test.ts точными файлами задачи. Уже добавленные в индекс изменения останавливают рецепт, ошибка git add тоже; пустой diff не создаёт коммит. Обычные проверки коммита включены. До активации офлайн испытайте успех, отказ и отсутствующий результат.

Не добавляйте git commit --no-verify к каждому Edit. Проверки коммита оставьте включёнными. При рекурсии сузьте триггер или измените проверку вместо стандартного обхода. До автокоммитов изучите индекс и владение задачей.

### Хук 7. Добавление актуального контекста при запуске (SessionStart и additionalContext)

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Текущая ветка: $BRANCH. Незакоммиченные изменения:\\n$STATUS\\\"}\""
          }
        ]
      }
    ]
  }
}
```

**Как работает:** при старте сессии команда получает текущую ветку и первые десять строк списка незакоммиченных изменений. Результат возвращается в JSON в поле `additionalContext`, которое Claude Code добавляет в контекст сессии.

<figure>
<blockquote>
<p>Используйте additionalContext для сведений о текущем состоянии среды или только что выполненной операции. Например, передавайте текущую ветку, цель развёртывания, активные feature flags, правила проекта для тестирования изменённого файла или внешние данные — открытые issues и последние результаты CI.</p>
</blockquote>
<figcaption>— Anthropic, справочник по хукам, https://code.claude.com/docs/en/hooks</figcaption>
</figure>

В этот хук также можно добавить сведения о последнем коммите, статусе GitHub Actions, текущем спринте в Linear или Jira и открытых задачах репозитория.

## 3 частые ошибки при настройке хуков

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Первая проблема: exit 1 может не остановить действие, и Claude продолжит работу. Вторая: длительная команда может превысить таймаут в 60 секунд. Третья: хук получает окружение процесса Claude Code, но не обязательно переменные из `.env` проекта. Учитывайте эти особенности при настройке и проверке команд.

</div>

### Ошибка 1. Хук с exit 1 не блокирует действие

Если hook-команда завершилась с exit 1, Claude Code может воспринять это как неблокирующую ошибку и продолжить действие. Например, проверка пути через `jq -r '.tool_input.file_path'` может завершиться ошибкой, если значение пустое или равно null. Тогда защитный хук не остановит редактирование `.env`.

Чтобы избежать этого, проверяйте входные значения и заранее задавайте поведение для пустого поля. Не рассчитывайте, что exit 1 заблокирует действие: для блокировки используйте предусмотренный для неё exit code.

```bash
# Шаблон безопасного начала и конца:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... твоя логика ...
exit 0   # или || exit 0 в одну строку
```

### Ошибка 2. Хук превышает таймаут

Если после редактирования запускать eslint --fix для большого файла, обработка может занять несколько секунд. В документации Claude Code указан стандартный таймаут хука в 60 секунд. При серии действий длительные проверки могут задержать работу и завершиться по таймауту.

Для тяжёлой фоновой задачи можно запустить команду с `&`, чтобы хук не ждал её завершения:

```bash
# Тяжёлая операция в background:
npx eslint --fix "$FILE" &
exit 0
```

### Ошибка 3. Хук не получает переменные из окружения проекта автоматически

Хук запускается в shell-процессе Claude Code. Он видит переменные окружения, доступные этому процессу, но значения из `.env` проекта автоматически не появляются. Если команда зависит, например, от `OPENAI_API_KEY` из `.env`, файл нужно загрузить явно.

**Решение:** подключите `.env` в команде хука или используйте инструмент загрузки переменных окружения.

```bash
# Прокидывание env проекта в hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Или через dotenv:
dotenv -e .env -- node my-validator.js
```

В монорепозитории разные пакеты могут использовать отдельные `.env`. В таком случае укажите рабочую папку явно и выполните `cd "$PWD"` в начале команды.

## Как проверить хуки до коммита?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Хук можно запустить как обычную shell-команду: передать тестовый JSON через stdin и проверить exit code и stderr. Такая проверка помогает обнаружить ошибки до того, как команда будет запущена Claude Code.

</div>

**Простейшая ручная проверка.** Скопируйте команду из JSON, откройте терминал и передайте ей JSON, аналогичный входным данным Claude Code:

```bash
# Тест хука 1 (защита .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
  echo "BLOCKED" >&2
  exit 2
} || exit 0
'
echo "Exit code: $?"
```

Ожидаемый результат — `BLOCKED` в stderr и `Exit code: 2`. Если команда вернула exit 0, проверьте шаблон grep.

**Проверьте три ситуации:**

1.  **Ожидаемая блокировка** — `.env` для хука 1 и `git push --force main` для хука 3.
2.  **Допустимое действие** — `README.md` для хука 1 и `git push origin feature-branch` для хука 3.
3.  **Граничные случаи** — пустой ввод, null и неожиданные символы. Хук должен завершиться с подходящим кодом или выдать понятную ошибку, а не молча пропустить проблему.

**Где проверять экспериментальные хуки.** Для проб используйте `.claude/settings.local.json`, который не включается в Git. Например, временно добавьте в `PostToolUse` команду с `echo`, чтобы выяснить, срабатывает ли matcher, прежде чем включать настоящее действие в проектный `settings.json`.

```json
// Эксперимент в settings.local.json - пишет в /tmp/hook-debug.log:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
          }
        ]
      }
    ]
  }
}
```

После нескольких правок проверьте `/tmp/hook-debug.log`. В нём должны появиться файлы, для которых сработал хук. Если журнал пуст, проверьте matcher и настройки события.

## Что делать дальше?

<div class="aside" data-semantic="key-takeaway" role="note" aria-label="Ключевые выводы раздела">

Ключевой вывод

Начните с `PostToolUse` и exit 0: такие хуки выполняют действие после инструмента, не блокируя его. Позже добавьте `PreToolUse` и exit 2 для ограничений вроде защиты `.env` или запрета force-push. По мере переноса проверяемых ограничений в хуки `CLAUDE.md` можно оставить для описания контекста проекта.

</div>

На ближайшую неделю можно запланировать пять шагов:

1.  **Просмотрите `CLAUDE.md`** и найдите слова «никогда», «запрещено», «всегда» и «обязательно». Такие формулировки могут указывать на правила, которые стоит проверять автоматически.
2.  **Начните с `PostToolUse` и exit 0.** Это более мягкий вариант: он выполняет действие после инструмента и не блокирует его. Примеры — prettier (хук 2), освобождение порта (хук 5) и коммит после тестов (хук 6).
3.  **Добавляйте `PreToolUse` с exit 2, когда готовы проверять блокировки.** Например, можно запретить изменение `.env` (хук 1) или force-push (хук 3). Перед использованием тщательно проверьте grep: ошибочное условие может остановить допустимое действие.
4.  **Проверяйте команды вручную перед коммитом**, как описано выше. Это помогает находить ошибки до запуска через Claude Code.
5.  **Используйте `settings.local.json` для экспериментов.** На первых этапах замените настоящее действие командой `echo`.

После настройки хуков можно прочитать, [как откатить изменения в Claude Code](https://ailoot.ai/blog/ru/claude-code-undo-changes/), если проверка пропустила нежелательное изменение. Для работы с контекстом проекта между сессиями см. [Второй мозг в Claude Code](https://ailoot.ai/blog/ru/claude-code-second-brain/). Переиспользуемые инструкции проекта описаны в гайде [Claude Code Skills](https://ailoot.ai/blog/ru/claude-code-skills-library/).

<div class="section" role="doc-bibliography" aria-label="Официальные источники">

### Источники

- [Справочник Claude Code Hooks — официальная документация Anthropic](https://code.claude.com/docs/en/hooks)
- [Документация Anthropic по памяти Claude Code и CLAUDE.md](https://code.claude.com/docs/en/memory)
- [Автоматизация рабочих процессов с помощью хуков — официальный гайд Anthropic](https://code.claude.com/docs/en/hooks-guide)

</div>



Если вам нужен доступ к нейросетям через Ailoot, ознакомьтесь с [Ailoot](https://ailoot.ai/).

## Вопросы и ответы

### Почему hook должен прочитать входной JSON только один раз?

Stdin — поток: первый jq потребляет его, следующие получают EOF. Сохраните вход и извлекайте поля команды и результата из копии. Неизвестный или отсутствующий результат непроверен: автокоммит нужно пропустить.

### Создают ли regex-примеры hooks границу безопасности?

Нет. Они проверяют отдельные события и узнаваемые строки. Перенаправления оболочки, алиасы, скрипты, MCP и другой путь записи могут обойти их. Для жёстких пределов нужны права файлов, ограниченные ключи, sandbox и защищённые ветки.


## Ailoot · Компания и портфель её проектов

Ailoot опирается на накопленный опыт работы, большую клиентскую базу, стабильный сервис и постоянный контроль KPI.

Компания и портфель её проектов: миллионы клиентов; продажи на миллиарды ежемесячно.

Этот опыт Ailoot использует для API-доступа к нейросетям в продуктах клиентов, агентах, приложениях и автоматизациях.
