Почему это важно именно вам
Представьте: вы диктуете агенту переработать письмо партнёру. Агент старается — правит тон, убирает лишнее, добавляет конкретику. Вы говорите «сохрани», и файл перезаписывается. Потом вы открываете письмо и видите, что агент зачем-то убрал ключевое условие по срокам. Или добавил неуместный абзац про «взаимовыгодное сотрудничество». Ошибка уже в файле, старая версия потеряна, нужно разбираться задним числом.
Проблема не в том, что агент плохой. Проблема в том, что между командой «правь» и действием «файл перезаписан» нет ни одной точки контроля. Вы работаете напрямую на продакшне, без черновика. В разработке это называется «деплоить прямо на прод без ревью» — и разработчики давно придумали, как с этим бороться. Хуки.
PreToolUse hook в Claude Code — это механизм, который срабатывает до того, как агент реально записывает файл. Вы можете вставить туда любую проверку: другой агент оценивает изменение, скрипт проверяет структуру, простое правило фиксирует нарушение. Если проверка прошла — файл сохраняется. Если нет — Claude получает отказ и объяснение. Это не git-хук и не CI/CD — это живая проверка прямо внутри рабочей сессии, до записи на диск.
Что это такое
В Claude Code есть система хуков — точек, в которых можно перехватить действие агента и вставить свою логику. Один из типов называется PreToolUse: он срабатывает перед тем, как Claude использует конкретный инструмент. Инструменты в Claude Code — это Edit, Write, Bash, WebSearch и другие. Нас сейчас интересуют Edit и Write: именно через них агент меняет файлы на диске.
Хорошая аналогия — юридическая виза на документах. В компании бывает так: контракт перед подписанием обязан пройти юридический отдел. Генеральный может дать добро, но без визы юриста документ не выходит. PreToolUse работает так же: агент хочет записать файл, но сначала этот файл проходит через вашу «юридическую проверку». Если там всё хорошо — файл записывается. Если нет — возвращается на доработку.
Важная деталь, которую легко перепутать: это не git pre-commit hook. Git pre-commit — это хук в системе контроля версий, он срабатывает когда вы делаете git commit. То, о чём мы говорим сегодня, — это хук внутри Claude Code, он срабатывает когда Claude пытается сохранить файл в рамках текущей сессии. Git про это ничего не знает. Это принципиально разные вещи на разных уровнях.
Ключевая идея
PreToolUse срабатывает до записи файла — не после. Это не журнал изменений и не откат, это ворота. Агент не может обойти хук, он просто получит отказ вместо подтверждения.
Как работает на практике
Хуки настраиваются в файле settings.json. Он лежит в .claude/settings.json внутри вашего рабочего каталога или в ~/.claude/settings.json для глобальных настроек. Мы работаем с проектным — он влияет только на этот каталог.
Шаг 1. Открыть settings.json
# Проверить, что файл существует
ls .claude/settings.json
Если файла нет — создайте пустой:
{}
Если файл уже есть с другим содержимым — добавим секцию hooks к существующему содержимому.
Шаг 2. Добавить секцию hooks
Базовая структура хука выглядит так:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 .claude/hooks/doc_review.py"
}
]
}
]
}
}
Разберём каждое поле:
PreToolUse— тип события: «перед использованием инструмента»matcher— фильтр по имени инструмента.Edit|Writeзначит: срабатывать при попытке использовать инструмент Edit или Writetype: "command"— тип хука: запустить внешнюю командуcommand— что запустить. В нашем случае Python-скрипт
Где находится .claude/
Папка .claude/ создаётся в корне вашего рабочего каталога, когда вы первый раз запускаете Claude Code там. Если её нет — создайте вручную: mkdir -p .claude/hooks
Шаг 3. Написать скрипт проверки
Создайте файл .claude/hooks/doc_review.py. Этот скрипт будет получать информацию о том, что агент собирается сохранить, и принимать решение.
Вот простой рабочий пример — скрипт проверяет текстовые документы на наличие стоп-слов и слишком коротких версий:
#!/usr/bin/env python3
import sys
import json
# Читаем входные данные от Claude Code
data = json.load(sys.stdin)
tool_name = data.get("tool_name", "")
tool_input = data.get("tool_input", {})
# Получаем имя файла и новое содержимое
file_path = tool_input.get("file_path", "")
new_content = tool_input.get("content", "") or tool_input.get("new_string", "")
# Проверяем только документы (не код, не конфиги)
doc_extensions = [".md", ".txt", ".docx"]
is_doc = any(file_path.endswith(ext) for ext in doc_extensions)
if not is_doc:
# Не документ — пропускаем без проверки
sys.exit(0)
# Список стоп-слов (замените своими)
stop_phrases = [
"взаимовыгодное сотрудничество",
"в рамках текущей парадигмы",
"синергетический эффект",
"уважаемые партнёры",
]
found_issues = []
for phrase in stop_phrases:
if phrase.lower() in new_content.lower():
found_issues.append(f"Стоп-фраза: «{phrase}»")
# Проверка: письмо не должно быть короче 50 слов
word_count = len(new_content.split())
if word_count < 50 and word_count > 0:
found_issues.append(f"Слишком короткий текст: {word_count} слов (минимум 50)")
if found_issues:
# Возвращаем ошибку — Claude получит этот текст и будет вынужден исправить
result = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Проверка документа не прошла:\n"
+ "\n".join(f"- {issue}" for issue in found_issues)
}
}
print(json.dumps(result))
sys.exit(0) # блокировка задаётся полем permissionDecision, код выхода при этом 0
# Всё хорошо — разрешаем сохранение
sys.exit(0)
Что здесь происходит:
- Claude Code передаёт скрипту через stdin JSON с данными об инструменте
- Скрипт проверяет: это документ или нет, есть ли стоп-фразы, нормальная ли длина
- Если проблем нет — sys.exit(0), файл сохраняется
- Если есть проблемы — sys.exit(2) с объяснением в JSON, Claude получает описание проблемы и должен её устранить
exit code имеет значение
sys.exit(0) — разрешить. sys.exit(1) — ошибка скрипта (не блокирует). sys.exit(2) — заблокировать действие и вернуть агенту описание проблемы. Не перепутайте: exit 1 не заблокирует запись, а exit 2 — заблокирует.
Шаг 4. Сделать скрипт исполняемым
chmod +x .claude/hooks/doc_review.py
Шаг 5. Проверить в работе
Запустите сессию Claude Code и попросите что-нибудь записать в файл с расширением .md, включив туда стоп-фразу. Например:
Напиши в файл test.md короткое письмо со словами "взаимовыгодное сотрудничество"
Агент попытается записать файл через Write, хук перехватит попытку, скрипт найдёт стоп-фразу, вернёт блокировку с объяснением. Claude увидит причину отказа и начнёт переписывать — уже без стоп-фразы. Файл запишется только после того, как все проверки пройдут чисто.
Реальный кейс: агент-ревьюер вместо скрипта
Скрипт с жёсткими правилами — это только один вариант. Более мощный подход: хук запускает другой Claude, который читает документ и оценивает его.
#!/usr/bin/env python3
import sys
import json
import subprocess
data = json.load(sys.stdin)
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
new_content = tool_input.get("content", "")
# Проверяем только письма и отчёты
if not any(file_path.endswith(ext) for ext in [".md", ".txt"]):
sys.exit(0)
# Запускаем Claude как ревьюера
review_prompt = f"""Ты редактор деловых текстов. Оцени этот документ:
{new_content}
Ответь строго в формате JSON:
{{"approved": true/false, "issues": ["проблема 1", "проблема 2"]}}
Блокируй (approved: false) только если:
- Есть явные канцеляризмы и пустые фразы
- Потеряны конкретные факты или числа
- Тон неуместен для делового письма
Не блокируй из-за стиля или личных предпочтений."""
result = subprocess.run(
["claude", "-p", review_prompt],
capture_output=True, text=True, timeout=30
)
try:
review = json.loads(result.stdout.strip())
if not review.get("approved", True):
issues = review.get("issues", [])
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Ревьюер отклонил документ:\n"
+ "\n".join(f"- {i}" for i in issues)
}
}
print(json.dumps(output))
sys.exit(2)
except (json.JSONDecodeError, KeyError):
# Если ревьюер не ответил в нужном формате — пропускаем
sys.exit(0)
sys.exit(0)
Этот вариант дороже по времени (каждое сохранение добавляет несколько секунд на вызов Claude), но гораздо умнее жёстких правил. Ревьюер понимает контекст, а не просто ищет строки.
Когда использовать скрипт, а когда Claude-ревьюер
Скрипт с правилами: быстро, дёшево, предсказуемо — хорош для формальных требований (стоп-слова, минимальная длина, обязательные поля). Claude-ревьюер: медленнее, стоит токенов, но понимает смысл — хорош для качественных требований (тон, логика, полнота). Начните со скрипта, добавьте Claude-ревьюер только для важных документов.
Частые ошибки
Ошибка 1: Поставить хук на все инструменты подряд
Если написать matcher: ".*", хук будет срабатывать на каждое действие агента — включая чтение файлов, поиск, вызов bash. Это не только замедляет работу, но и ломает многие операции, которые не имеют отношения к документам.
Правильно: matcher: "Edit|Write" — только инструменты записи файлов. Если нужно ограничить дальше — можно добавить проверку расширения файла внутри скрипта, как показано в примере выше.
Ошибка 2: Возвращать exit 1 вместо exit 2
Exit code 1 означает «скрипт завершился с ошибкой». Claude Code интерпретирует это как технический сбой хука, а не как намеренную блокировку. Файл при этом может записаться. Exit code 2 означает именно «заблокировать действие». Перепутать их — значит получить хук, который молча не работает.
Ошибка 3: Блокировать слишком агрессивно
Если хук блокирует любое несовершенство, агент начнёт зацикливаться: переписывает файл, снова получает блокировку, снова переписывает. Через несколько итераций сессия встаёт или Claude начинает игнорировать ревью. Хук должен блокировать реальные проблемы, не эстетические разногласия. Формулируйте критерии блокировки чётко и минималистично.
Ошибка 4: Не обрабатывать файлы, которые хук не должен трогать
Если ваш хук не добавляет проверку «это вообще документ?», он будет запускаться при каждой записи любого файла. Агент создаёт временный JSON-файл с промежуточными данными — хук запускается. Агент обновляет конфиг — хук запускается. Добавьте в начало скрипта явный фильтр по расширению файла и сразу выходите с exit 0, если файл не попадает в вашу зону контроля.
Хук не защита от злого умысла
PreToolUse хук запускается в том же окружении, что и агент. Если что-то пошло не так и хук упал с непредвиденной ошибкой, поведение зависит от настроек. Хук — это удобный инструмент контроля качества, не система безопасности. Для критичных документов всегда используйте версионирование (git) дополнительно к хукам.
Когда нужно / когда нет
PreToolUse хуки нужны, когда:
-
Агент регулярно редактирует документы, которые потом уходят внешним адресатам. Письма партнёрам, отчёты клиентам, коммерческие предложения — любой текст, который имеет последствия вне вашей системы.
-
У вас есть корпоративные стандарты, которые агент периодически нарушает. Если одно и то же стоп-слово или запрещённая формулировка всплывает снова и снова — это сигнал поставить проверку.
-
Документы создаются в пакетном режиме. Агент генерирует 20 отчётов по шаблону — хук проверяет каждый и отклоняет некачественные до того, как они попадут в папку с готовыми файлами.
-
Вы работаете с несколькими агентами или субагентами, которые редактируют общие документы. Хук — дополнительный слой консистентности.
PreToolUse хуки не нужны, когда:
-
Агент работает с кодом, конфигами, данными. Хук для документов не должен останавливать техническую работу.
-
Задача разовая и несрочная. Если вы попросили агента один раз подготовить одно письмо — просмотрите результат сами, не тратьте время на настройку хука.
-
Стоимость ложного срабатывания низкая. Если файл легко откатить (есть git) или переписать (это черновик) — хук добавляет больше трений, чем пользы.
-
Хук был бы умнее вас. Если вы не можете чётко сформулировать критерии «хорошего» и «плохого» документа — хук тоже не сможет. Сначала формулируйте критерии для себя, потом автоматизируйте их.
Хуки как документация ожиданий
Хорошо написанный хук — это заодно и спецификация требований к документу. Когда вы описываете, что должен проверять скрипт, вы явно формулируете, каким должен быть документ. Это полезно само по себе, даже если никакой автоматизации не будет.
Связь с другими уроками
День 41 — система хуков: все типы событий. Сегодня мы работаем с PreToolUse — одним из нескольких типов. В фазе 5 вы разберёте полную картину: PostToolUse, Stop, Notification и другие. PreToolUse — самый понятный вход в тему хуков, после этого урока остальные типы будут логичны.
День 24 — субагенты и их инструменты. Хук может запускать не только скрипт, но и субагента-ревьюера. Если вы разобрались, как устроены субагенты в .claude/agents/, — вы можете использовать их как проверяющую логику в хуке. Это более мощная, но и более дорогая комбинация.
День 36 — fork и параллельные сессии. Когда несколько агентов работают с одними документами в параллельных сессиях, хуки помогают обеспечить единые стандарты качества. Без хуков каждый агент пишет по-своему; с хуком — у всех один фильтр на выходе.
Задание на сегодня
Добавьте в свой рабочий каталог один PreToolUse хук с простой проверкой текстовых файлов.
Конкретные шаги:
- Создайте папку
.claude/hooks/если её ещё нет. - Создайте файл
.claude/hooks/doc_check.py— возьмите первый пример из урока как основу. - Замените стоп-фразы в скрипте на реальные слова, которые вы сами хотите блокировать (хотя бы три штуки из вашей практики).
- Добавьте в
.claude/settings.jsonсекциюhooksпо шаблону из урока. - Запустите Claude Code и попросите записать в любой
.mdфайл текст с одной из ваших стоп-фраз. - Убедитесь, что агент получил блокировку и переписал текст без стоп-фразы.
Критерий «выполнено»: агент попытался записать файл со стоп-фразой, получил отказ от хука, самостоятельно убрал стоп-фразу и записал исправленную версию. Вы при этом ничего не делали вручную.
Резюме
- PreToolUse хук перехватывает попытку агента записать файл до того, как запись произошла — это контрольная точка, а не откат
- Конфигурируется в
.claude/settings.jsonчерез секциюhooks, matcherEdit|Writeловит оба инструмента записи - Хук запускает внешний скрипт через stdin/stdout и exit code: 0 = разрешить, 2 = заблокировать с объяснением, 1 = техническая ошибка
- Хук можно сделать умным — запустить внутри него Claude как ревьюера, но это медленнее и стоит токенов
- Не путать с git pre-commit: git-хук срабатывает при коммите, PreToolUse — при каждой попытке записи файла в сессии